Не могу передать в "mapfile" bash ... но почему?

13

Я просто хочу получить все файлы в определенном каталоге в массив bash (при условии, что ни один из файлов не имеет новой строки в имени):

Так:

myarr=()
find . -maxdepth 1  -name "mysqldump*" | mapfile -t myarr; echo "${myarr[@]}"

Пустой результат!

Если я делаю окольный способ использования файла, временного или иного:

myarr=()
find . -maxdepth 1  -name "mysqldump*" > X
mapfile -t myarray < X
echo "${myarray[@]}"

Результат!

Но почему не mapfileчитает правильно из трубы?

Дэвид Тонхофер
источник
Отличные ответы всем, спасибо всем. Интересно, как стратегия выполнения конвейера (каждая часть выполняется в отдельном процессе) просачивается «вверх» и изменяет кажущийся смысл кода, в основном молча помещая «local» перед каждой переменной, появляющейся в конвейере. На языке, который является чем-то отличным от сумасшедшего клея для других программ, мы надеемся, что это будет ошибкой.
Дэвид Тонхофер
2
Если вы передадите код для проверки оболочки , вы получите предупреждения: SC2030 : «Модификация var локальна (для подоболочки вызвана конвейером)» и SC2031 : «var была изменена в подоболочке. Это изменение может быть потеряно». , Превосходно.
Дэвид Тонхофер
Зачем использовать findи mapfileздесь вообще, а не просто myarr=(mysqldump*)? Это будет работать даже с именами файлов с пробелами и символами новой строки.
Блэкджек
1
Только что заметил, что нужно включить nullglobпараметр ( shopt -s nullglob), myarr=(mysqldump*)чтобы не заканчивать массив ('mysqldump*')в случае, если файлы не совпадают.
Дэвид Тонхофер

Ответы:

25

От man 1 bash:

Каждая команда в конвейере выполняется как отдельный процесс (т. Е. В подоболочке).

Такие подоболочки наследуют переменные из основной оболочки, но они независимы. Это означает, что mapfileв вашей исходной команде действует самостоятельно myarr. Затем echo(находясь вне трубы) печатает пустой myarr(который является основной оболочкой myarr).

Эта команда работает по-другому:

find . -maxdepth 1 -name "mysqldump*" | { mapfile -t myarr; echo "${myarr[@]}"; }

В этом случае mapfileи echoработают одинаково myarr(что не является основной оболочкой myarr).

Чтобы изменить основную оболочку, myarrвы должны mapfileточно запустить основную оболочку. Пример:

myarr=()
mapfile -t myarr < <(find . -maxdepth 1 -name "mysqldump*")
echo "${myarr[@]}"
Камил Мачоровски
источник
Добавлена ​​ссылка на «процесс замены», как указано в ответе Атти, в случае, если у посетителя есть момент TL; DR.
Дэвид Тонхофер
11

Bash запускает команды конвейера в среде подоболочек, поэтому любые переменные и т. Д., Которые происходят внутри него, не видны остальной части оболочки.

Dash (Debian /bin/sh) и busybox shпохожи, в то время как zsh и ksh выполняют последнюю часть в основной оболочке. В Bash вы можете использовать shopt -s lastpipeто же самое, но это работает только тогда, когда управление заданиями отключено, поэтому по умолчанию не используется в интерактивных оболочках.

Так:

$ bash -c 'x=a; echo b | read x; echo $x'
a
$ bash -c 'shopt -s lastpipe; x=a; echo b | read x; echo $x'
b

( readи mapfileимеют ту же проблему.)

В качестве альтернативы (и как уже упоминалось в Attie), используйте процесс подстановки , который работает как обобщенный канал и поддерживается в Bash, ksh и zsh.

$ bash -c 'x=a; read x < <(echo b); echo $x'
b

POSIX оставляет это неопределенным, если части конвейера работают в подоболочках или нет, так что нельзя сказать, что какая-либо из оболочек будет «неправильной» в этом.

ilkkachu
источник
2
Если вы отключите контроль заданий bash, вы также можете использовать lastpipe в интерактивной оболочке:set +m; shopt -s lastpipe; x=a; echo b | read x; echo $x; set -m
Cyrus
@Cyrus, да, я забыл подробности, спасибо
ilkkachu
9

Как отметил Камиль, каждый элемент в конвейере - это отдельный процесс.

Вы можете использовать следующую подстановку процесса для findзапуска в другом процессе с mapfileвызовом, оставшимся в вашем текущем интерпретаторе, позволяющем myarrвпоследствии доступ к :

myarr=()
mapfile -t myarr < <( find . -maxdepth 1  -name "mysqldump*" )
echo "${myarr[@]}"

b < <( a )будет действовать аналогично с a | bточки зрения того, как конвейер подключен - разница в том, что bвыполняется « здесь ».

Attie
источник