it-roy-ru.com

Как использовать `while read` (Bash) для чтения последней строки в файле, если в конце файла нет символа новой строки?

Допустим, у меня есть следующий скрипт 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, который у меня был ранее, то есть без жесткого кодирования местоположения входного файла?

43
Mathias Bynens

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

DONE=false
until $DONE ;do
read || DONE=true
echo $REPLY
done
13
netcoder

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

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

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

  • Файлы, которые начинаются или заканчиваются пустыми строками
  • Линии, начинающиеся или заканчивающиеся пробелами
  • Файлы без завершающей строки
31
Adam Bryzak

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

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

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

Замечания: 

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

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

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

4
Jahid

Вместо read попробуйте использовать GNU Coreutils like tee , cat и т.д.

из стандартного

readvalue=$(tee)
echo $readvalue

из файла

readvalue=$(cat filename)
echo $readvalue
2
prabhakaran9397

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

Таким образом, вы можете использовать errorlevel of 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 ])
1
vaab

Ответ @ 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="[email protected]";
    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="[email protected]";
    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, иначе просто переводит аргументы)

0
unsynchronized

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

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

Это работает потому, что даже если цикл while заканчивается как «тест» из выходных данных read с ненулевым кодом, read по-прежнему заполняет встроенную переменную $REPLY (или любые переменные, которые вы выбираете назначить с read).

0
Olli K