Как прочитать полную строку в цикле «for» с пробелами


130

Я пытаюсь запустить 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.

Ответы:


179

forЦикл разделяется, когда он видит любые пробелы, такие как пробел, табуляция или перевод строки. Итак, вы должны использовать IFS (Внутренний разделитель полей) :

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'?
Рен

2
$заставляет разрывы строк работать внутри строки. Это также должно быть сделано local IFS=$'\n'внутри функции.
Энди Рэй

1
@Renn, который $'...'известен как « цитирование ANSI-C »
αғsнιη

82

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

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

Я ожидаю, что ваша команда будет выдавать по одному слову в строке (это произошло, когда я проверил это с помощью собственного файла). Если что-то еще происходит, я не уверен, что может быть причиной этого.


8
Это также является хорошей альтернативой 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может использоваться с |гарантией неформатированного вывода по одной строке
AndreyS Щербаков

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

Проверенная работа, не тот лайнер, который вы, вероятно, хотите, но это невозможно сделать элегантно.


1
здесь-строка! Самое элегантное из всех других решений ИМО
Элиран Малка

5

Вот небольшое расширение ответа Митчелла Керри, которое мне нравится из-за небольшого количества побочных эффектов, которое позволяет избежать необходимости устанавливать переменную:

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

1
Вы могли бы удалить -name '*'и было бы то же самое, нет?
августа

1

Я бы написал это так:

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

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


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

0

Mapfile - это удобный способ чтения строк из файла в индексированный массив, не такой переносимый, как чтение, но немного быстрее. Используя цикл for, вы избегаете создания подоболочки.

#!/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

Дэндальф очень близко подошел к функциональному решению, но НИКОГДА НЕ ДОЛЖНО пытаться присвоить результат неизвестных 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.