Если вам нужно сохранить выходные данные и вы хотите массив, mapfile
это легко.
Во-первых, подумайте, нужно ли вообще сохранять выходные данные вашей команды. Если нет, просто запустите команду.
Если вы решите, что хотите прочитать вывод команды в виде массива строк, это правда, что один из способов сделать это - отключить глобализацию, установить IFS
разбиение на строки, использовать подстановку команд в (
)
синтаксисе создания массива и выполнить сброс IFS
после этого, что сложнее, чем кажется. Ответ Тердона охватывает некоторые из этого подхода. Но я предлагаю вам использовать встроенную команду mapfile
оболочки Bash для чтения текста в виде массива строк. Вот простой случай, когда вы читаете из файла:
mapfile < filename
Это читает строки в массив с именем MAPFILE
. Без перенаправления ввода вы будете читать из стандартного ввода оболочки (обычно вашего терминала) вместо файла с именем . Для чтения в массив, отличный от значения по умолчанию , передайте его имя. Например, это читает в массив :< filename
filename
MAPFILE
lines
mapfile lines < filename
Другое поведение по умолчанию, которое вы можете изменить, состоит в том, что символы новой строки в концах строк остаются на месте; они появляются как последний символ в каждом элементе массива (если только ввод не завершился без символа новой строки, в этом случае последний элемент не имеет такового). Чтобы сжать эти новые строки, чтобы они не появлялись в массиве, передайте -t
опцию mapfile
. Например, это читает в массив records
и не записывает завершающие символы новой строки в его элементы массива:
mapfile -t records < filename
Вы также можете использовать -t
без передачи имени массива; то есть он также работает с неявным именем массива MAPFILE
.
mapfile
Оболочки встроенной опоры и другие варианты, и он в качестве альтернативы может быть вызван в качестве readarray
. Запустите help mapfile
(и help readarray
) для деталей.
Но вы не хотите читать из файла, вы хотите читать из вывода команды. Чтобы добиться этого, используйте процесс подстановки . Эта команда читает строки из команды some-command
с arguments...
аргументами командной строки и помещает их в mapfile
массив по умолчанию MAPFILE
с удаленными завершающими символами новой строки:
mapfile -t < <(some-command arguments...)
Подстановка процесса заменяется реальным именем файла, из которого можно прочитать результаты выполнения. Файл является именованным каналом, а не обычным файлом, и в Ubuntu он будет именоваться как (иногда с другим номером, чем ), но вам не нужно заботиться о деталях, потому что оболочка заботится об этом все за кадром.<(some-command arguments...)
some-command arguments...
/dev/fd/63
63
Вы можете подумать, что можете отказаться от замены процесса, используя вместо этого, но это не сработает, потому что, когда у вас есть конвейер из нескольких команд, разделенных Bash, запускает все команды в подоболочках . Таким образом оба и запускаются в своих собственных средах , инициализируемых, но отдельно от среды оболочки, в которой вы запускаете конвейер. В субоболочке , где работает, то массив имеет получить населенную, но затем этот массив отбрасываются , когда концы команд. никогда не создается и не изменяется для вызывающей стороны.some-command arguments... | mapfile -t
|
some-command arguments...
mapfile -t
mapfile -t
MAPFILE
MAPFILE
Вот как выглядит пример в ответе Тердона , если вы используете mapfile
:
mapfile -t dirs < <(find . -type d)
for d in "${dirs[@]}"; do
echo "DIR: $d"
done
Вот и все. Вам не нужно проверять, IFS
был ли он установлен , отслеживать, было ли оно установлено и с каким значением, устанавливать его на новую строку, а затем сбрасывать или сбрасывать его позже. Вам не нужно отключить подстановку (например, с set -f
) , - которые действительно необходимы , если вы хотите использовать этот метод серьезно, так как имена файлов могут содержать *
, ?
и [
--then повторно включить его ( set +f
) впоследствии.
Вы также можете полностью заменить этот конкретный цикл одной printf
командой - хотя это на самом деле не является преимуществом mapfile
, так как вы можете сделать это независимо от того, используете ли вы mapfile
или другой метод для заполнения массива. Вот более короткая версия:
mapfile -t < <(find . -type d)
printf 'DIR: %s\n' "${MAPFILE[@]}"
Важно помнить, что, как упоминает Тердон , построчная работа не всегда уместна и не будет корректно работать с именами файлов, которые содержат переводы строк. Я рекомендую не называть файлы таким образом, но это может произойти, в том числе и случайно.
На самом деле не существует универсального решения.
Вы просили «общую команду для всех сценариев» и подход, основанный на использовании mapfile
нескольких подходов к этой цели, но я призываю вас пересмотреть ваши требования.
Задача, показанная выше, лучше достигается с помощью одной find
команды:
find . -type d -printf 'DIR: %p\n'
Вы также можете использовать внешнюю команду, например, sed
добавить DIR:
в начало каждой строки. Это, возможно, несколько уродливо, и в отличие от этой команды find, она добавит дополнительные «префиксы» в имена файлов, содержащие символы новой строки, но она работает независимо от ее ввода, поэтому она как бы соответствует вашим требованиям и все же предпочтительнее считывать вывод в переменная или массив :
find . -type d | sed 's/^/DIR: /'
Если вам нужно перечислить, а также выполнить действие для каждого найденного каталога, например, запустить some-command
и передать путь к каталогу в качестве аргумента, то find
вы тоже можете это сделать:
find . -type d -print -exec some-command {} \;
В качестве другого примера, давайте вернемся к общей задаче добавления префикса к каждой строке. Предположим, я хочу увидеть результат, help mapfile
но нумеровать строки. Я бы на самом деле не использовал mapfile
для этого ни какой-либо другой метод, который считывает его в переменную оболочки или массив оболочки. Предположим help mapfile | cat -n
, не дает нужного мне форматирования, я могу использовать awk
:
help mapfile | awk '{ printf "%3d: %s\n", NR, $0 }'
Чтение всех выходных данных команды в одну переменную или массив иногда полезно и целесообразно, но имеет серьезные недостатки. Вам не только приходится сталкиваться с дополнительной сложностью использования вашей оболочки, когда существующая команда или комбинация существующих команд может уже делать то, что вам нужно, либо лучше, но весь вывод команды должен храниться в памяти. Иногда вы можете знать, что это не проблема, но иногда вы можете обрабатывать большой файл.
Альтернатива, которую обычно пытаются (а иногда и делают правильно), - читать входные данные построчно read -r
в цикле. Если вам не нужно сохранять предыдущие строки при работе с более поздними строками, и вам нужно использовать длинный ввод, тогда это может быть лучше, чем mapfile
. Но этого также следует избегать в тех случаях, когда вы можете просто передать его команде, которая может выполнить эту работу, что и происходит в большинстве случаев .