Прочитать файл, ориентированный на строку, который может не заканчиваться символом новой строки

11

У меня есть файл с именем, /tmp/urlFileгде каждая строка представляет URL-адрес. Я пытаюсь прочитать из файла следующее:

cat "/tmp/urlFile" | while read url
do
    echo $url
done

Если последняя строка не заканчивается символом новой строки, эта строка не будет прочитана. Мне было интересно, почему?

Можно ли прочитать все строки, независимо от того, заканчиваются ли они новой строкой или нет?

Тим
источник
8
Это обсуждается в разделе Почему использование цикла оболочки для обработки текста считается плохой практикой? (каким-то образом это сделать)
Стефан Шазелас
2
Ха @ Стефан, мне там нравится TBD ;-).
Стивен Китт
2
Еще один способ добавить завершающий символ новой строки, если он отсутствует; awk 1 /tmp/urlFile.. такawk 1 /tmp/urlFile | while ...
Муру
@ Муру, это лучший ответ, чем любой другой здесь.
Wildcard
1
Поскольку вы спрашиваете, почему он не читается: stackoverflow.com/a/729795/1968
Конрад Рудольф

Ответы:

13

Вы бы сделали:

while IFS= read -r url || [ -n "$url" ]; do
  printf '%s\n' "$url"
done < url.list

(по сути, этот цикл добавляет недостающий символ новой строки в последней (не) строке).

Смотрите также:

Стефан Шазелас
источник
Спасибо. Я прочитал связанные статьи, и, возможно, я что-то пропустил, почему «этот цикл добавляет недостающий символ новой строки в последней (не) строке»?
Тим
1
@Tim То, что Стефан, кажется, означает, что он добавляет отсутствующий символ новой строки в вывод, так как все printfвызовы здесь имеют \n.
Сергей Колодяжный
6

Это, кажется, решается частично с помощью readarray -t:

readarray -t urls "/tmp/urlFile"
for url in "${urls[@]}"; do
    printf '%s\n' "$url"
done

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

DopeGhoti
источник
Спасибо. Какая часть это решает, а какая нет?
Tim
Это решает проблему с отсутствием завершающего символа новой строки, но вводит потенциальную новую проблему с очень большими файлами, потому что сначала считывает файл в массив, который затем должен быть повторен.
DopeGhoti
1
@ DopeGhoti Это хорошая информация - могу ли я предложить вам добавить ее прямо в ответ?
RJHunter
Ответ был так исправлен.
DopeGhoti
5

По определению текстовый файл состоит из последовательности строк. Строка заканчивается символом новой строки. Таким образом, текстовый файл заканчивается символом новой строки, если он не пустой.

readВстроенный предназначено только для чтения текстовых файлов. Вы не передаете текстовый файл, поэтому вы не можете надеяться, что он будет работать без проблем. Оболочка читает все строки - пропускаются лишние символы после последней строки.

Если у вас есть потенциально искаженный входной файл, в котором может отсутствовать последняя строка, вы можете добавить в него новую строку, просто чтобы быть уверенным.

{ cat "/tmp/urlFile"; echo; } | 

Файлы, которые должны быть текстовыми файлами, но без последней строки, часто создаются редакторами Windows. Это обычно происходит в сочетании с окончаниями строк Windows, которые являются CR LF, в отличие от Unix LF. Символы CR редко бывают полезными в любом месте и ни в коем случае не могут появляться в URL, поэтому их следует удалить.

{ <"/tmp/urlFile" tr -d '\r'; echo; } | 

В случае, если входной файл правильно сформирован и заканчивается новой echoстрокой , добавляется дополнительная пустая строка. Поскольку URL не могут быть пустыми, просто игнорируйте пустые строки.

Обратите внимание, что readстроки не читаются простым способом. Он игнорирует начальные и конечные пробелы, что для URL, вероятно, желательно. Он обрабатывает обратную косую черту в конце строки как escape-символ, в результате чего следующая строка соединяется с первой минус последовательность backslash-newline, что определенно нежелательно. Таким образом, вы должны передать -rопцию read. Это очень, очень редко, readчтобы быть правильным, а не read -r.

{ <"/tmp/urlFile" tr -d '\r'; echo; } | while read -r url
do
  if [ -z "$url" ]; then continue; fi
  
done
Жиль "ТАК - перестань быть злым"
источник
3

Ну, readвозвращает ложное значение, если оно встречает конец файла перед новой строкой, но даже если это так, оно все равно присваивает прочитанное значение. Итак, мы можем проверить, readвозвращает ли последний вызов что-то еще, кроме пустой строки, и обработать это как обычно. Таким образом, выход из цикла возможен только после readвозврата false и пустой строки:

#!/bin/sh
while IFS= read -r line || [ "$line" ]; do 
    echo "line: $line"
done

$ printf 'foo\nbar' | sh ./read.sh 
line: foo
line: bar
$ printf 'foo\nbar\n' | sh ./read.sh 
line: foo
line: bar
ilkkachu
источник
1

Другой способ будет выглядеть так:

Когда чтение достигает конца файла вместо конца строки, он читает данные и присваивает их переменным, но завершается с ненулевым статусом. Если ваш цикл построен "во время чтения; делать вещи; сделано

Таким образом, вместо непосредственного тестирования состояния завершения чтения, проверьте флаг и задайте для команды чтения этот флаг из тела цикла. Таким образом, независимо от состояния выхода чтения, выполняется все тело цикла, потому что чтение было просто одной из списка команд в цикле, как и любой другой, а не решающим фактором того, будет ли цикл выполняться вообще.

DONE=false
until $DONE ;do
read || DONE=true
echo $REPLY 
done < /tmp/urlFile

Ссылается отсюда .

Hunter.S.Thompson
источник
1
кот "/ tmp / urlFile" | пока читаешь URL
делать
    echo $ url
сделанный

Это бесполезное использованиеcat .

По иронии судьбы вы можете заменить этот catпроцесс на что-то действительно полезное: инструмент, который есть в системах POSIX для добавления отсутствующего перевода строки и преобразования файла в правильный текстовый файл POSIX.

sed -e '$ a \' "/ tmp / urlFile" | пока читаешь -r url
делать
    printf "% s \ n" "$ {url}"
сделанный

дальнейшее чтение

JdeBP
источник
1
POSIX не определяет поведение sed, когда ввод не заканчивается символом новой строки; также когда есть строки больше, чем LINE_MAX, в то время как поведение readуказано в этих случаях.
Стефан Шазелас