Как я могу удалить завершающий перевод строки в bash?

10

Я ищу что-то похожее на Perl chomp. Я ищу команду, которая просто печатает ввод, минус последний символ, если это новая строка:

$ printf "one\ntwo\n" | COMMAND_IM_LOOKING_FOR ; echo " done"
one
two done
$ printf "one\ntwo" | COMMAND_IM_LOOKING_FOR ; echo " done"
one
two done

(Подстановка команд в Bash и Zsh удаляет все завершающие новые строки, но я ищу что-то, что максимально удаляет одну завершающую новую строку.)

Флимм
источник

Ответы:

9

Это должно работать:

printf "one\ntwo\n" | awk 'NR>1{print PREV} {PREV=$0} END{printf("%s",$0)}' ; echo " done"

Скрипт всегда печатает предыдущую строку вместо текущей, а последняя строка обрабатывается по-разному.

Что это делает более подробно:

  1. NR>1{print PREV} Распечатать предыдущую строку (кроме первого раза).
  2. {PREV=$0}Сохраняет текущую строку в PREVпеременной.
  3. END{printf("%s",$0)} Наконец, напечатайте последнюю строку без перевода строки.

Также обратите внимание, что это приведет к удалению не более одной пустой строки в конце (без поддержки удаления "one\ntwo\n\n\n").

LatinSuD
источник
15

Вы можете использовать perlбез chomp:

$ printf "one\ntwo\n" | perl -0 -pe 's/\n\Z//'; echo " done"
one
two done

$ printf "one\ntwo" | perl -0 -pe 's/\n\Z//'; echo " done"
one
two done

Но почему бы не использовать chompсебя:

$ printf "one\ntwo\n" | perl -pe 'chomp if eof'; echo " done"
cuonglm
источник
4

Если вам нужен точный эквивалент chomp, первым методом, который мне приходит в голову, является решение awk, которое уже выложил LatinSuD . Я добавлю некоторые другие методы, которые не реализуют, chompно реализуют некоторые общие задачи, которые chompчасто используются для.

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

echo "$(printf 'one\ntwo') done"
echo "$(printf 'one\ntwo\n') done"
echo "$(printf 'one\ntwo\n\n') done"
echo "$(printf 'one\ntwo\n\n\n\n\n\n\n\n\n\n') done"

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

sed '$ s/$/ done/'

¹ Однако это не работает со всеми реализациями sed: sed - это инструмент обработки текста, а файл, который не пуст и не заканчивается символом перевода строки, не является текстовым файлом.

Жиль "ТАК - перестань быть злым"
источник
Это не совсем эквивалентно chomp, так как chompудаляет не более одного завершающего символа новой строки.
Flimm
@Flimm Да, наиболее очевидным точным эквивалентом chompбудет решение awk, которое уже выложил LatinSuD. Но во многих случаях chompэто всего лишь инструмент для выполнения работы, и я предоставляю способы выполнения некоторых общих задач. Позвольте мне обновить мой ответ, чтобы уточнить это.
Жиль "ТАК - перестань быть злым"
1

Другой perlподход. Он считывает весь ввод в память, поэтому он не может быть хорошей идеей для больших объемов данных (используйте cuonglm или awkподход для этого):

$ printf "one\ntwo\n" | perl -0777pe 's/\n$//'; echo " done"
one
two done
Тердон
источник
Спасибо, @ StéphaneChazelas, исправлено. Почему-то этот переключатель меня всегда смущает !
Terdon
0

Я поймал это где-то в репозитории github, но не могу найти где

удаление замыкающего пустые линии-SED

#!/bin/bash
#
# Delete all trailing blank lines.
# From http://sed.sourceforge.net/sed1line.txt
#
# Version: 1.3.0
# Created: 2011-01-02
# Updated: 2015-01-25
# Contact: Joel Parker Henderson (joel@joelparkerhenderson.com)
# License: GPL
##
set -euf
sed -e :a -e '/^\n*$/{$d;N;ba' -e '}'
Брэд Паркс
источник
0

Аннотация

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

$ printf 'one\ntwo\n' | 

     awk '{ printf( "%s%s" , NR>1?"\n":"" , $0 ) }';   echo " done"

one
two done

Другие решения

Если мы работали с файлом, мы можем просто обрезать один символ из него (если он заканчивается на новой строке):

removeTrailNewline () {[[$ (tail -c 1 "$ 1")]] || truncate -s-1 "$ 1"; }

Это быстрое решение, так как нужно прочитать только один символ из файла, а затем удалить его напрямую ( truncate), не читая весь файл.

Однако при работе с данными из stdin (потока) все данные должны быть прочитаны. И это "потребляется", как только это прочитано. Нет возврата (как с усечением). Чтобы найти конец потока, нам нужно прочитать его до конца. В этот момент нет возможности вернуться назад к входному потоку, данные уже «использованы». Это означает, что данные должны храниться в некотором виде буфера до тех пор, пока мы не совпадем с концом потока, а затем что-то сделаем с данными в буфере.

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

переменная

Наивным решением было бы захватить весь ввод в переменную:

FilterOne(){ filecontents=$(cat; echo "x");        # capture the whole input
             filecontents=${filecontents%x};       # Remove the "x" added above.
             nl=$'\n';                             # use a variable for newline.
             printf '%s' "${filecontents%"$nl"}";  # Remove newline (if it exists).
       }

printf 'one\ntwo'     | FilterOne ; echo 1done
printf 'one\ntwo\n'   | FilterOne ; echo 2done
printf 'one\ntwo\n\n' | FilterOne ; echo 3done

Память

Можно загрузить весь файл в память с помощью sed. В sed невозможно избежать завершающего перевода строки на последней строке. GNU sed может не печатать завершающий символ новой строки, но только если в исходном файле его уже нет. Так что нет, простой sed не может помочь.

За исключением GNU awk с -zопцией:

sed -z 's/\(.*\)\n$/\1/'

С помощью awk (любой awk) хлебать весь поток, и printfэто без завершающего перевода строки.

awk '    { content = content $0 RS } 
     END { gsub( "\n$", "", content ); printf( "%s", content ) }
    '

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

Две строки в памяти

В awk мы можем обработать две строки в цикле, сохранив предыдущую строку в переменной и напечатав текущую:

awk 'NR>1{print previous} {previous=$0} END {printf("%s",$0)}'

Прямая обработка

Но мы могли бы сделать лучше.

Если мы печатаем текущую строку без новой строки и печатаем новую только тогда, когда существует следующая строка, мы обрабатываем по одной строке за раз, и последняя строка не будет иметь завершающий символ новой строки:

awk 'NR == 1 {printf ("% s", $ 0); далее}; {printf ("\ n% s", $ 0)} '

Или написано другим способом:

awk 'NR>1{ print "" }; { printf( "%s", $0 ) }'

Или:

awk '{ printf( "%s%s" , NR>1?"\n":"" , $0 ) }'

Так:

$ printf 'one\ntwo\n' | awk '{ printf( "%s%s" , NR>1?"\n":"" , $0 ) }'; echo " done"
one
two done
Исаак
источник