У мене є змінна, яка містить багаторядковий вихід команди. Який найефективніший спосіб прочитати вихідний рядок за рядком зі змінної?
Наприклад:
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