Подтвердить что ты не робот

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

Предположим, что у меня есть следующий Bash script:

while read SCRIPT_SOURCE_LINE; do
  echo "$SCRIPT_SOURCE_LINE"
done

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

Я искал для решения и нашел это:

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

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

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

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

4b9b3361

Ответ 1

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

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

Ответ 2

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

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

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

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

Ответ 3

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

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

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

Примечание:

Ответ 4

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

из stdin

readvalue=$(tee)
echo $readvalue

из файла

readvalue=$(cat filename)
echo $readvalue

Ответ 5

Основная проблема здесь заключается в том, что 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 ])

Ответ 6

@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 иначе переводит аргументы)

Ответ 7

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

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

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