Аннотация
Печатайте строки без новой строки, добавляйте новую строку, только если есть еще одна строка для печати.
$ 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
chomp
, так какchomp
удаляет не более одного завершающего символа новой строки.