Як я можу видалити нижній рядок у bash?


10

Я шукаю те, що поводиться як Перл 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").


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"

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, але не можу знайти де

delete-trailing-blank-lines-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 () {[[$ (хвіст -c 1 "$ 1")]] || обрізати -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 може уникнути друку останнього рядка, але тільки якщо у вихідному файлі його вже немає. Отже, ні, простий сед не може допомогти.

За винятком GNU awk з -zопцією:

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

За допомогою awk (будь-якого awk) промацуйте весь потік, і printfце без зворотного рядка.

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

Завантаження цілого файлу в пам’ять може бути не дуже хорошою ідеєю, це може зайняти багато пам'яті.

Два рядки в пам'яті

У дивовижному режимі ми можемо обробити два рядки в циклі, зберігаючи попередній рядок у змінній та роздрукувавши даний:

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
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.