У меня есть переменная, которая содержит многострочный вывод команды. Какой самый эффективный способ читать вывод построчно из переменной?
Например:
jobs="$(jobs)"
if [ "$jobs" ]; then
# read lines from $jobs
fi
У меня есть переменная, которая содержит многострочный вывод команды. Какой самый эффективный способ читать вывод построчно из переменной?
Например:
jobs="$(jobs)"
if [ "$jobs" ]; then
# read lines from $jobs
fi
Ответы:
Вы можете использовать цикл while с подстановкой процесса:
while read -r line
do
echo "$line"
done < <(jobs)
Оптимальный способ чтения многострочной переменной - установить пустую IFS
переменную и printf
переменную с завершающим символом новой строки:
# Printf '%s\n' "$var" is necessary because printf '%s' "$var" on a
# variable that doesn't end with a newline then the while loop will
# completely miss the last line of the variable.
while IFS= read -r line
do
echo "$line"
done < <(printf '%s\n' "$var")
Примечание. В соответствии с проверкой оболочки sc2031 использование подстановки процесса предпочтительнее, чем конвейер, чтобы избежать [тонкого] создания подоболочки.
Также, пожалуйста, поймите, что присвоение имени переменной jobs
может вызвать путаницу, так как это также имя обычной команды оболочки.
echo
на printf %s
так, чтобы ваш сценарий работал даже при нестандартном вводе.
/tmp
каталог был доступен для записи, поскольку он полагается на возможность создания временного рабочего файла. Если вы когда-нибудь окажетесь в системе с ограниченным доступом /tmp
, доступной только для чтения (и не подлежащей изменению вами), вы будете рады возможности использовать альтернативное решение, например, с printf
конвейером.
printf "%s\n" "$var" | while IFS= read -r line
Чтобы обработать вывод командной строки построчно ( объяснение ):
jobs |
while IFS= read -r line; do
process "$line"
done
Если у вас уже есть данные в переменной:
printf %s "$foo" | …
printf %s "$foo"
почти идентичен echo "$foo"
, но печатает $foo
буквально, тогда как echo "$foo"
может интерпретироваться $foo
как опция команды echo, если она начинается с a -
, и может расширять последовательности обратной косой черты $foo
в некоторых оболочках.
Обратите внимание, что в некоторых оболочках (ash, bash, pdksh, но не в ksh или zsh) правая часть конвейера выполняется в отдельном процессе, поэтому любая переменная, заданная в цикле, теряется. Например, следующий скрипт подсчета строк выводит 0 в этих оболочках:
n=0
printf %s "$foo" |
while IFS= read -r line; do
n=$(($n + 1))
done
echo $n
Обходной путь - поместить оставшуюся часть скрипта (или, по крайней мере, ту часть, которая нуждается в значении $n
цикла) в список команд:
n=0
printf %s "$foo" | {
while IFS= read -r line; do
n=$(($n + 1))
done
echo $n
}
Если действия с непустыми строками достаточно хороши, а ввод невелик, вы можете использовать разбиение слов:
IFS='
'
set -f
for line in $(jobs); do
# process line
done
set +f
unset IFS
Объяснение: установка IFS
на одну новую строку приводит к разделению слов только на новых строках (в отличие от любого символа пробела при настройке по умолчанию). set -f
отключает подстановку (т. е. расширение по шаблону), что в противном случае могло бы произойти в результате подстановки команды $(jobs)
или переменной substitutino $foo
. for
Петля действует на все части $(jobs)
, которые являются все непустые строки в выходных данных команды. Наконец-то восстановим сглаживание и IFS
настройки.
local IFS=something
. Это не повлияет на значение глобальной области видимости. IIRC, unset IFS
не возвращает вас к значениям по умолчанию (и, конечно, не работает, если это не было заранее задано по умолчанию).
Проблема: если вы используете цикл while, он будет работать в подоболочке и все переменные будут потеряны. Решение: использовать для цикла
# change delimiter (IFS) to new line.
IFS_BAK=$IFS
IFS=$'\n'
for line in $variableWithSeveralLines; do
echo "$line"
# return IFS back if you need to split new line by spaces:
IFS=$IFS_BAK
IFS_BAK=
lineConvertedToArraySplittedBySpaces=( $line )
echo "{lineConvertedToArraySplittedBySpaces[0]}"
# return IFS back to newline for "for" loop
IFS_BAK=$IFS
IFS=$'\n'
done
# return delimiter to previous value
IFS=$IFS_BAK
IFS_BAK=
while read
цикл в bash означает, что цикл while находится в подоболочке, поэтому переменные не являются глобальными. while read;do ;done <<< "$var"
делает тело цикла не подоболочкой (У недавнего bash есть возможность поместить тело cmd | while
цикла не в подоболочку, как всегда было в ksh.)
В последних версиях bash используйте mapfile
или readarray
для эффективного чтения вывода команд в массивы
$ readarray test < <(ls -ltrR)
$ echo ${#test[@]}
6305
Отказ от ответственности: ужасный пример, но вы можете придумать лучшую команду для использования, чем вы сами
readarray
функцию и вызовите функцию несколько раз.
jobs="$(jobs)"
while IFS= read
do
echo $REPLY
done <<< "$jobs"
Рекомендации:
-r
это тоже хорошая идея; Это предотвращает \` interpretation... (it is in your links, but its probably worth mentioning, just to round out your
IFS = `(что важно для предотвращения потери пробела)
Вы можете использовать <<< для простого чтения из переменной, содержащей разделенные строкой данные:
while read -r line
do
echo "A line of input: $line"
done <<<"$lines"
while IFS= read
.... Если вы хотите предотвратить \ интерпретацию, тогда используйтеread -r