Почему некоторые оболочки `read` не могут прочитать всю строку из файла в` / proc`?


19

В некоторых Bourne-подобные оболочкам, то readвстроенный не может прочитать всю строку из файла /proc(команда ниже должны быть запущены в zsh, замените $=shellс $shellдругими оболочками):

$ for shell in bash dash ksh mksh yash zsh schily-sh heirloom-sh "busybox sh"; do
  printf '[%s]\n' "$shell"
  $=shell -c 'IFS= read x </proc/sys/fs/file-max; echo "$x"'       
done
[bash]
602160
[dash]
6
[ksh]
602160
[mksh]
6
[yash]
6
[zsh]
6
[schily-sh]
602160
[heirloom-sh]
602160
[busybox sh]
6

readСтандарт требует, чтобы стандартным вводом был текстовый файл , вызывает ли это требование различное поведение?


Прочитайте определение текстового файла POSIX , я делаю некоторые проверки:

$ od -t a </proc/sys/fs/file-max 
0000000   6   0   2   1   6   0  nl
0000007

$ find /proc/sys/fs -type f -name 'file-max'
/proc/sys/fs/file-max

Там нет NULсимвола в содержании /proc/sys/fs/file-max, а также findсообщил об этом как обычный файл (это ошибка в find?).

Я думаю, что оболочка сделала что-то под капотом, как file:

$ file /proc/sys/fs/file-max
/proc/sys/fs/file-max: empty

Ответы:


31

Проблема в том, что эти /procфайлы в Linux выглядят как текстовые файлы stat()/fstat(), но не ведут себя как таковые.

Поскольку это динамические данные, вы можете выполнить только один read()системный вызов (по крайней мере, для некоторых из них). Делая больше, чем один, вы можете получить два куска двух разных типов содержимого, поэтому вместо этого кажется, что секунда read()для них просто ничего не возвращает (имеется в виду конец файла) (если только вы не lseek()вернетесь к началу (и только к началу)).

readУтилита должна считывать содержимое файлов один байт в то время , чтобы убедиться , что не читать мимо символа новой строки. Вот что dashделает:

 $ strace -fe read dash -c 'read a < /proc/sys/fs/file-max'
 read(0, "1", 1)                         = 1
 read(0, "", 1)                          = 0

В некоторых оболочках bashесть оптимизация, чтобы избежать необходимости делать так много read()системных вызовов. Сначала они проверяют, является ли файл доступным для поиска, и, если это так, читают чанками, так как они знают, что могут поместить курсор назад сразу после новой строки, если они прочитали после него:

$ strace -e lseek,read bash -c 'read a' < /proc/sys/fs/file-max
lseek(0, 0, SEEK_CUR)                   = 0
read(0, "1628689\n", 128)               = 8

При этом у bashвас по-прежнему будут проблемы с файлами процедур, размер которых превышает 128 байт и которые можно прочитать только за один системный вызов чтения.

bashтакже, кажется, отключить эту оптимизацию при использовании этой -dопции.

ksh93продвигает оптимизацию еще дальше, чтобы стать поддельным. ksh93 readвыполняет поиск назад, но запоминает дополнительные данные, которые он прочитал для следующего read, поэтому следующий read(или любая из его других встроенных функций, которые читают данные как catили head), даже не пытается получить readданные (даже если эти данные были изменены другие команды между ними):

$ seq 10 > a; ksh -c 'read a; echo test > a; read b; echo "$a $b"' < a
1 2
$ seq 10 > a; sh -c 'read a; echo test > a; read b; echo "$a $b"' < a
1 st

Ах, да, straceобъяснение на основе гораздо легче понять!
Стивен Китт

Спасибо, динамические данные имеют смысл. Так как же оболочка обнаруживает свои динамические данные? Если я это сделаю cat /proc/sys/fs/file-max | ..., проблема исчезла.
cuonglm

3
Оболочка не обнаруживает это. Тот факт, что это динамические данные, означает, что procfsони не могут обрабатывать несколько последовательных read(2)вызовов одного и того же файла; поведение не зависит от оболочки. Использование catи конвейерная обработка работают, потому что catчитает файл достаточно большими кусками; readвстроенная оболочка затем читает из канала по одному символу за раз.
Стивен Китт

1
Есть немного грязный обходной путь mksh. read -N 10 a < /proc/sys/fs/file-max
Ипор Сирсер

1
@IporSircer. В самом деле. Похоже, что похожий обходной путь работает zsh: read -u0 -k10(или использовать sysread; $mapfile[/proc/sys/fs/file-max]не работает, так как эти файлы не могут быть mmapотредактированы). В любом случае с любой оболочкой всегда можно a=$(cat /proc/sys/fs/file-max). С некоторыми, в том числе mksh, zshи ksh93, a=$(</proc/sys/fs/file-max)также работает и не разворачивает процесс для чтения.
Стефан Шазелас

9

Если вам интересно узнать почему? это так, вы можете увидеть ответ в исходниках ядра здесь :

    if (!data || !table->maxlen || !*lenp || (*ppos && !write)) {
            *lenp = 0;
            return 0;
    }

По сути, поиск ( *pposне 0) не реализован для reads ( !write) значений sysctl, которые являются числами. Всякий раз, когда выполняется чтение /proc/sys/fs/file-max, соответствующая подпрограмма __do_proc_doulongvec_minmax()вызывается из записи file-maxв таблице конфигурации в том же файле.

Другие записи, такие как /proc/sys/kernel/poweroff_cmdреализованные через proc_dostring()которые разрешают поиск, так что вы можете делать dd bs=1это и читать из вашей оболочки без проблем.

Обратите внимание, что, начиная с ядра 2.6, большинство операций/proc чтения было реализовано с помощью нового API под названием seq_file, и это поддерживает поиск, поэтому, например, чтение /proc/statне должно вызывать проблем. /proc/sys/Реализация, как мы можем видеть, не использовать этот API.


3

С первой попытки это выглядит как ошибка в оболочках, которые возвращают меньше, чем реальная оболочка Борна или ее производные (sh, bosh, ksh, семейная реликвия).

Оригинальная оболочка Bourne Shell пытается прочитать блок (64 байта), более новые варианты оболочки Bourne Shell читают 128 байтов, но они начинают читать снова, если нет символа новой строки.

Предпосылки: / procfs и подобные реализации (например, смонтированный /etc/mtabвиртуальный файл) имеют динамическое содержимое, и stat()вызов не вызывает повторного создания динамического содержимого в первую очередь. По этой причине размер такого файла (от чтения до EOF) может отличаться от того, что stat()возвращается.

Учитывая, что стандарт POSIX требует, чтобы утилиты ожидали коротких чтений в любое время, программное обеспечение, которое считает, что a, read()который возвращает меньше, чем упорядоченный объем байтов, является признаком EOF, не работает. Правильно реализованная утилита вызывает read()второй раз, если возвращает меньше ожидаемого - до тех пор, пока не будет возвращено 0. В случае readвстроенного, конечно, было бы достаточно, чтобы прочитать до EOF или пока не NLбудет видно.

Если вы запускаете trussили клон фермы, вы должны быть в состоянии проверить это неправильное поведение для оболочек, которые возвращаются только 6в вашем эксперименте.

В этом особом случае это ошибка ядра Linux, см .:

$ sdd -debug bs=1 if= /proc/sys/fs/file-max 
Simple copy ...
readbuf  (3, 12AC000, 1) = 1
writebuf (1, 12AC000, 1)
8readbuf  (3, 12AC000, 1) = 0

sdd: Read  1 records + 0 bytes (total of 1 bytes = 0.00k).
sdd: Wrote 1 records + 0 bytes (total of 1 bytes = 0.00k).

Ядро Linux возвращает 0 со вторым, readи это, конечно, неправильно.

Вывод: Оболочки, которые сначала пытаются прочитать достаточно большой кусок данных, не вызывают эту ошибку ядра Linux.


ОК, вышел ответ с новой проверкой на ошибку ядра Linux.
Шили

Это не ошибка, это особенность!
Гунтрам Блом поддерживает Монику

Это действительно странное утверждение.
Шили

Было бы особенность, если бы это было задокументировано. Читая kernel.org/doc/Documentation/filesystems/proc.txt , я не вижу документации для поведения. Тем не менее, он явно работает как задумано разработчиком, поэтому, если это следует считать ошибкой, это ошибка в дизайне, а не в реализации.
Чарльз Даффи

0

Файлы в / proc иногда используют символ NULL для разделения полей внутри файла. Кажется, что чтение не может справиться с этим.

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.