Як прочитати повний рядок у циклі "for" з пробілами


128

Я намагаюся запустити forцикл для файлу і хочу показати весь рядок. Але замість цього відображається лише останнє слово. Я хочу повний рядок.

for j in `cat ./file_wget_med`

do
echo $j

done

результат після запуску:

Found.

Ось мої дані:

$ cat file_wget_med
2013-09-11 14:27:03 ERROR 404: Not Found.

Відповіді:


178

forцикл розбивається, коли він бачить будь-який пробіл, наприклад пробіл, вкладку чи новий рядок. Отже, ви повинні використовувати IFS (Internal Field Separator) :

IFS=$'\n'       # make newlines the only separator
for j in $(cat ./file_wget_med)    
do
    echo "$j"
done
# Note: IFS needs to be reset to default!

5
І потурбуйтеся про одиничні лапки в IFS, оскільки IFS = $ "\ n" розділить також рядки "nn". Я встановив, що встановити IFS надійніше, ніж використовувати складніший синтаксис або функції.
erm3nda

23
мабуть, рекомендується unset IFSпісля цього, щоб інші команди не впливали на цю ж оболонку.
Борис Даппен

2
Що $означає IFS=$'\n'?
Ren

2
$змушує обриви ліній працювати всередині рядка. Це також слід робити з local IFS=$'\n'функцією всередині.
Енді Рей

1
@ Renn $'...'відомий " ANSI-C
Quiting

82

forпетлі розділені на будь-який пробіл (пробіл, вкладка, новий рядок) за замовчуванням; найпростіший спосіб працювати над одним рядком одночасно - це використовувати while readцикл, який розбивається на нові рядки:

while read i; do echo "$i"; done < ./file_wget_med

Я б очікував, що ваша команда випхне по одному слову за рядком (саме так сталося, коли я перевірив це з власним файлом). Якщо щось інше відбувається, я не впевнений, що може це викликати.


7
Це також є хорошою альтернативою for i in `ls`; do echo $1; done, по конвеєру висновок команді в той час як: ls|while read i; do echo $i; done. Це працює для імен файлів / папок з пробілами, не потребуючи змін змінних середовища. Дуже гарна відповідь.
jishi

поки перший і останній рядки не дуже обробляли, не так добре, як для циклу for IFS
grantbow

@jishi Оскільки він в основному розроблений для відображення і відповідає розміру віконця терміналів і тому подібному, lsне вважається безпечним для використання з |(оператор труби). findє більш гнучким, крім того, що для цієї мети є більш безпечним.
SeldomNeedy

3
Це ВЕЛИКИЙ відповідь, я використовував його, щоб перетворити список, який я мав, до записів PLIST для програми IOS, яку я розробляв на mac OSX. Я замінив введення файлу, перенісши в буфер обміну за допомогою pbpaste ->pbpaste | while read t; do echo "<string>$t</string>"; done
Big Rich

3
ls -1може бути використаний |для гарантування неформатованого виходу на одну лінію на одну лінію
Андрій Шербаков,

19
#!/bin/bash
files=`find <subdir> -name '*'`
while read -r fname; do
    echo $fname
done <<< "$files"

Перевірено працюючий, не той вкладиш, який ви, мабуть, хочете, але зробити це вишукано неможливо.


1
тут-рядок! найелегантніші з усіх інших рішень IMO
Еліран Малька

5

Ось невелике розширення відповіді Мітчелла Керрі, що мені подобається через малу область побічних ефектів, що дозволяє уникнути встановлення змінної:

#!/bin/bash
while read -r fname; do
    echo $fname
done <<< "`find <subdir>`"

1
Ви можете зняти, -name '*'і це було б саме, ні?
wjandrea

1

Я б написав це так:

cat ./file_wget_med | while read -r j
do
    echo $j
done

оскільки це вимагає найменших змін у вихідному сценарії (за винятком використання рішення IFS, але воно змінює bashповедінку не тільки для цього оператора управління циклом).


1
Труби і catтут непотрібні. whileloop сприймає перенаправлення чудово, тому зазвичай пишеться такwhile IFS= read -r line; do...done < input.txt
Сергій Колодяжний

0

Mapfile - це зручний спосіб зчитування рядків з файлу в індексований масив, не настільки портативний, як читання, але трохи швидше. Використовуючи для циклу, ви уникаєте створення підшару.

#!/bin/bash

mapfile -t < file.txt

for line in "${MAPFILE[@]}"; do
    echo $line
done

Майте на увазі, використовуючи трубопроводи, він буде містити цикл while в нижній частині корпусу. Зміни всередині циклу while, подібні до змінних, не поширюватимуться на зовнішню частину сценарію.

Приклад:

#!/bin/bash

a=0
printf %s\\n {0..5} | while read; do
  ((a++))
done
echo $a # 'a' will always be 0.

(Краще рішення):

#!/bin/bash

b=0
while read; do
  ((b++))
done < <(printf %s\\n {0..5})

echo $b # 'b' equal to 6 (works as expected).

-1

Dandalf реально наблизився до функціонального рішення, але НІКОЛИ НІКОЛИ не намагатися призначити результат невідомої кількості вхідних даних (тобто find ~/.gdfuse -name '*') змінним! Або, принаймні, намагатися зробити таке за допомогою змінної масиву; якщо ви наполягаєте на тому, щоб бути таким ледачим! Отже, ось рішення Дандальфа виконано без небезпечного маневру; і все в одному рядку

while read -r fname; do
  echo $fname;
done <<< `find ~/.gdfuse -name '*'

Привіт @odoncaoa, я намагався виконувати команду find без подвійних лапок, але це робить усі результати відображатися як один рядок. Мене плутають "невідомі обсяги введення" з цією командою та небезпеки. Чи можете ви навести приклад небезпеки та як теоретично вони могли відбутися? Я серйозно не бажаю лінуватися з цього приводу, мені просто зручніше в інших мовах програмування.
Дандальф
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.