Как использовать `while read` (Bash) для чтения последней строки в файле, если в конце файла нет символа новой строки? - PullRequest
46 голосов
/ 12 ноября 2010

Допустим, у меня есть следующий скрипт Bash:

while read SCRIPT_SOURCE_LINE; do
  echo "$SCRIPT_SOURCE_LINE"
done

Я заметил, что для файлов без перевода строки в конце это будет эффективно пропускать последнюю строку.

У меня естьискал решение и нашел это :

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

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

DONE=false
until $DONE ;do
read || DONE=true
# process $REPLY here
done < /path/to/file.in

Как я могу переписать это решение, чтобы оно работало точно так же, как цикл while, который у меня был ранее, то есть без жесткого кодирования местоположения входного файла?

Ответы [ 7 ]

36 голосов
/ 16 февраля 2011

Я использую следующую конструкцию:

while IFS= read -r LINE || [[ -n "$LINE" ]]; do
    echo "$LINE"
done

Работает практически со всем, кроме нулевых символов на входе:

  • Файлы, начинающиеся или заканчивающиеся пустыми строками
  • Строки, начинающиеся или заканчивающиеся пробелами
  • Файлы без завершающей строки
14 голосов
/ 12 ноября 2010

В вашем первом примере я предполагаю, что вы читаете с stdin .Чтобы сделать то же самое со вторым блоком кода, вам просто нужно удалить перенаправление и повторить команду $ REPLY:

DONE=false
until $DONE ;do
read || DONE=true
echo $REPLY
done
5 голосов
/ 14 июля 2015

Использование grep с циклом while:

while IFS= read -r line; do
  echo "$line"
done < <(grep "" file)

Использование grep . вместо grep "" пропустит пустые строки.

Примечание:

  1. Использование IFS= позволяет сохранить любой отступ строки.

  2. Вы должны почти всегда использовать опцию -r с read.

  3. Файл без новой строки в конце не является стандартным текстовым файлом Unix.

2 голосов
/ 03 ноября 2016

Вместо читать , попробуйте использовать GNU Coreutils , как tee , cat и т. Д.

изстандартный

readvalue=$(tee)
echo $readvalue

из файла

readvalue=$(cat filename)
echo $readvalue
1 голос
/ 06 марта 2017

Это шаблон, который я использовал:

while read -r; do
  echo "${REPLY}"
done
[[ ${REPLY} ]] && echo "${REPLY}"

, который работает, потому что даже если цикл while заканчивается, когда "тест" из read завершается с ненулевым значениемcode, read по-прежнему заполняет встроенную переменную $REPLY (или любые другие переменные, которые вы выбираете с помощью read).

1 голос
/ 03 января 2015

Основная проблема здесь в том, что read вернет уровень ошибки 1, когда он встретит EOF, даже если он все равно будет корректно передавать переменную.

Таким образом, вы можете использовать уровень ошибки read прямо вВаш цикл, в противном случае, последние данные не будут проанализированы.Но вы можете сделать это:

eof=
while [ -z "$eof" ]; do
    read SCRIPT_SOURCE_LINE || eof=true   ## detect eof, but have a last round
    echo "$SCRIPT_SOURCE_LINE"
done

Если вы хотите очень надежный способ разбора ваших строк, вы должны использовать:

IFS='' read -r LINE

Помните, что:

  • NUL-символ будет игнорироваться
  • , если вы будете использовать echo для имитации поведения cat, вам нужно будет принудительно echo -n при обнаружении EOF (вы можете использовать условие [ "$eof" == true ])
0 голосов
/ 07 июня 2016

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

DONE=false
NL=
until $DONE ;do
if ! read ; then DONE=true ; NL='-n ';fi
echo $NL$REPLY
done

Я использовал вариант этого, чтобы создать 2 функции, позволяющие передавать по тексту текст, который включает '[', чтобы сохранить радость grep. (Вы можете добавить другие переводы)

function grepfix(){
    local x="$@";
    if [[ "$x" == '-' ]]; then
      local DONE=false
      local xx=
      until $DONE ;do
         if ! IFS= read ; then DONE=true ; xx="-n "; fi
         echo ${xx}${REPLY//\[/\\\[}
      done
    else
      echo "${x//\[/\\\[}"
    fi
 }


 function grepunfix(){
    local x="$@";
    if [[ "$x" == '-' ]]; then
      local DONE=false
      local xx=
      until $DONE ;do
         if ! IFS= read ; then DONE=true ; xx="-n "; fi
         echo ${xx}${REPLY//\\\[/\[}
      done
    else
      echo "${x//\\\[/\[}"
    fi
 }

(передача - поскольку $ 1 включает pipe, в противном случае просто переводит аргументы)

...