У мене є змінна, яка містить багаторядковий вихід команди. Який найефективніший спосіб прочитати вихідний рядок за рядком зі змінної?
Наприклад:
jobs="$(jobs)"
if [ "$jobs" ]; then
# read lines from $jobs
fi
У мене є змінна, яка містить багаторядковий вихід команди. Який найефективніший спосіб прочитати вихідний рядок за рядком зі змінної?
Наприклад:
jobs="$(jobs)"
if [ "$jobs" ]; then
# read lines from $jobs
fi
Відповіді:
Ви можете використовувати цикл час із заміною процесу:
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")
Примітка. Відповідно до shellcheck 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)
або змінної субститутіно $foo
. for
Петля діє на всі частини $(jobs)
, які є всі непусті рядки в вихідних даних команди. Нарешті, відновіть глобулінг та IFS
налаштування.
local IFS=something
. Це не вплине на значення глобального масштабу. IIRC, unset IFS
не повертає вас до замовчування (і, звичайно, не працює, якщо раніше не було типовим).
Проблема: якщо ви використовуєте цикл, він запускається в нижній частині, і всі змінні будуть втрачені. Рішення: використовувати для циклу
# 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
Відмова: жахливий приклад, але ви можете придумати кращу команду для використання, ніж ls себе
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