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


11

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

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

Якщо останній рядок не закінчується символом нового рядка, цей рядок не буде прочитаний. Мені було цікаво, чому?

Чи можна прочитати всі рядки незалежно від того, закінчуються вони новим рядком чи ні?



2
Hah @ Stéphane Мені подобається TBD там ;-).
Стівен Кітт

2
Ще один спосіб додати останній новий рядок, якщо він відсутній; awk 1 /tmp/urlFile.. такawk 1 /tmp/urlFile | while ...
муру

@muru, це краща відповідь, ніж будь-який інший тут.
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

1
@DopeGhoti Це хороша інформація - чи можу я запропонувати вам додати її безпосередньо у відповідь?
RJHunter

Відповідь Та була змінена.
DopeGhoti

5

За визначенням текстовий файл складається з послідовності рядків. лінія закінчується символом нового рядка. Таким чином, текстовий файл закінчується символом нового рядка, якщо він не порожній.

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

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

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

Файли, які мають бути текстовими файлами, але у них відсутній остаточний рядок, часто створюються редакторами Windows. Зазвичай це поєднується з закінченнями рядків Windows, що є CR LF, на відміну від LF Unix. Символи CR рідко є корисними в будь-якому місці і не можуть з’являтися в URL-адресах ні в якому разі, тому їх слід видалити.

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

Якщо вхідний файл добре сформований і закінчується новим рядком, echoдодається додатковий порожній рядок. Оскільки URL-адреси не можуть бути порожніми, просто ігноруйте порожні рядки.

Зауважте також, що readрядки не читають прямо. Він ігнорує пробіли та пробіли, що для URL-адреси, ймовірно, бажано. Це трактує зворотну косу риску в кінці рядка як символ втечі, внаслідок чого наступний рядок з'єднується з першим мінусом послідовності нахил-новий рядок, що, безумовно, не бажано. Тож вам слід передати цей -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

1

Інший спосіб був би таким:

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

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

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

Посилання звідси .


1
кішка "/ tmp / urlFile" | під час читання URL-адреси
робити
    echo $ url
зроблено

Це марне використанняcat .

Як не дивно, ви можете замінити catпроцес тут чимось фактично корисним: інструментом, який система POSIX має для додавання відсутнього нового рядка та перетворення файлу у відповідний текстовий файл POSIX.

sed -e '$ a \' "/ tmp / urlFile" | під час читання -r URL
робити
    printf "% s \ n" "$ {url}"
зроблено

Подальше читання


1
Поведінка sed не визначена POSIX, коли вхід не закінчується символом нового рядка; також, коли є рядки більше LINE_MAX, тоді як поведінка readвказано в цих випадках.
Стефан Шазелас
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.