Вопрос: Bash: Итерация по строкам в переменной


Как правильно перебирать строки в bash либо в переменной, либо из вывода команды? Просто установка переменной IFS в новую строку работает для вывода команды, но не при обработке переменной, содержащей новые строки.

Например

#!/bin/bash

list="One\ntwo\nthree\nfour"

#Print the list with echo
echo -e "echo: \n$list"

#Set the field separator to new line
IFS=$'\n'

#Try to iterate over each line
echo "For loop:"
for item in $list
do
        echo "Item: $item"
done

#Output the variable to a file
echo -e $list > list.txt

#Try to iterate over each line from the cat command
echo "For loop over command output:"
for item in `cat list.txt`
do
        echo "Item: $item"
done

Это дает результат:

echo: 
One
two
three
four
For loop:
Item: One\ntwo\nthree\nfour
For loop over command output:
Item: One
Item: two
Item: three
Item: four

Как вы можете видеть, повторяя переменную или итерируя по cat команда печатает каждую из строк один за другим правильно. Однако первый цикл for печатает все элементы в одной строке. Есть идеи?


164
2018-05-16 12:14


Источник




Ответы:


Если bash, если вы хотите встраивать новые строки в строку, заключите строку с $'':

$ list="One\ntwo\nthree\nfour"
$ echo "$list"
One\ntwo\nthree\nfour
$ list=$'One\ntwo\nthree\nfour'
$ echo "$list"
One
two
three
four

И если у вас есть такая строка уже в переменной, вы можете прочитать ее по очереди:

while read -r line; do
    echo "... $line ..."
done <<< "$list"

204
2018-05-16 13:47



Просто отметьте, что done <<< "$list" имеет решающее значение - Jason Axelson
Причина done <<< "$list" имеет решающее значение, потому что это будет передавать «$ list» в качестве вклада в read - wisbucky
Мне нужно подчеркнуть это: ДВОЙНОЕ КВОТИРОВАНИЕ $list очень важно. - André Chalella
Как я могу это сделать в золе? Потому что я получаю syntax error: unexpected redirection, - atripes
Есть недостатки этого подхода, поскольку цикл while будет работать в подоболочке: любые переменные, назначенные в цикле, не будут сохраняться. - glenn jackman


Вы можете использовать while + read:

some_command | while read line ; do
   echo === $line ===
done

Btw. -e вариант echo является нестандартным. использование printf вместо этого, если вы хотите переносить.


46
2018-05-16 12:21



Обратите внимание: если вы используете этот синтаксис, переменные, назначенные внутри цикла, не будут вставлены после цикла. Как ни странно, <<< версия, предложенная glenn jackman делает работа с переменным присваиванием. - Sparhawk
@Sparhawk Да, это потому, что труба запускает подоболочку, выполняющую while часть. <<< версии нет (в новых версиях bash, по крайней мере). - maxelost


#!/bin/sh

items="
one two three four
hello world
this should work just fine
"

IFS='
'
count=0
for item in $items
do
  count=$((count+1))
  echo $count $item
done

18
2018-03-17 21:45



Это выводит все элементы, а не линии. - Tomasz Posłuszny
@ TomaszPosłuszny Нет, это выводит 3 (количество 3) строк на моей машине. Обратите внимание на настройку IFS. Вы также можете использовать IFS=$'\n' для того же эффекта. - jmiserez


Вот забавный способ сделать цикл for:

for item in ${list//\\n/
}
do
   echo "Item: $item"
done

Чуть более понятным и понятным будет:

cr='
'
for item in ${list//\\n/$cr}
do
   echo "Item: $item"
done

Но это слишком сложно, вам нужно только место:

for item in ${list//\\n/ }
do
   echo "Item: $item"
done

Вы $line переменная не содержит строк новой строки. Он содержит примеры \ с последующим n, Вы можете это ясно видеть:

$ cat t.sh
#! /bin/bash
list="One\ntwo\nthree\nfour"
echo $list | hexdump -C

$ ./t.sh
00000000  4f 6e 65 5c 6e 74 77 6f  5c 6e 74 68 72 65 65 5c  |One\ntwo\nthree\|
00000010  6e 66 6f 75 72 0a                                 |nfour.|
00000016

Замена заменяет их пробелами, которых достаточно для работы в циклах:

$ cat t.sh
#! /bin/bash
list="One\ntwo\nthree\nfour"
echo ${list//\\n/ } | hexdump -C

$ ./t.sh 
00000000  4f 6e 65 20 74 77 6f 20  74 68 72 65 65 20 66 6f  |One two three fo|
00000010  75 72 0a                                          |ur.|
00000013

Демо-версия:

$ cat t.sh
#! /bin/bash
list="One\ntwo\nthree\nfour"
echo ${list//\\n/ } | hexdump -C
for item in ${list//\\n/ } ; do
    echo $item
done

$ ./t.sh 
00000000  4f 6e 65 20 74 77 6f 20  74 68 72 65 65 20 66 6f  |One two three fo|
00000010  75 72 0a                                          |ur.|
00000013
One
two
three
four

6
2018-05-16 12:45



Интересно, можете ли вы объяснить, что здесь происходит? Похоже, вы заменяете \ n новой строкой ... В чем разница между исходной строкой и новой? - Alex Spurling
@Alex: обновил мой ответ - с более простой версией :-) - Mat
Я пробовал это, и он не работает, если у вас есть пробелы в вашем входе. В приведенном выше примере мы имеем «один \ ntwo \ nthree», и это работает, но оно терпит неудачу, если у нас есть «entry one \ nentry two \ nentry three», так как он также добавляет новую строку для пространства. - John Rocha
Просто отметим, что вместо определения cr вы можете использовать $'\n', - devios1