web-dev-qa-db-ja.com

bashで末尾の改行を削除するにはどうすればよいですか?

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でコマンドを置換すると、末尾の改行がすべて削除されますが、末尾の改行を最大1つ削除するものを探しています。)

10
Flimm

これはうまくいくはずです:

_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)}最後に、改行なしで最後の行を出力します。

また、これにより、末尾の最大1つの空行が削除されることに注意してください(_"one\ntwo\n\n\n"_の削除はサポートされていません)。

9
LatinSuD

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"
15
cuonglm

chompとまったく同じものが必要な場合、私の頭に浮かぶ最初の方法は LatinSuDが既に投稿したawkソリューション です。 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はテキスト処理ツールであり、空ではなく、改行文字で終わらないファイルはテキストファイルではありません。

別のPerlアプローチ。これは入力全体をメモリに読み込むので、大量のデータにはお勧めできません(cuonglmまたはawkアプローチを使用してください)。

$ printf "one\ntwo\n" | Perl -0777pe 's/\n$//'; echo " done"
one
two done
1
terdon

私はこれを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 ([email protected])
# License: GPL
##
set -euf
sed -e :a -e '/^\n*$/{$d;N;ba' -e '}'
0
Brad Parks

概要

改行なしで行を印刷し、印刷する別の行がある場合にのみ改行を追加します。

$ printf 'one\ntwo\n' | 

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

one
two done

その他の解決策

ファイルで作業している場合は、ファイルから1文字を切り捨てることができます(改行で終わる場合)。

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

ファイルからone文字のみを読み取り、ファイル全体を読み取らずに直接削除する(truncate)必要があるため、これは高速なソリューションです。

ただし、stdin(ストリーム)からのデータを処理するときは、データをすべて読み取る必要があります。そして、それが読み込まれるとすぐに「消費」されます。バックトラックなし(切り捨てと同様)。ストリームの終わりを見つけるには、ストリームの終わりまで読み取る必要があります。その時点では、入力ストリームに戻る方法はなく、データはすでに「消費」されています。これは、ストリームの最後に一致するまでデータmustが何らかの形式のバッファに格納され、その後、バッファ内のデータで何かを行うことを意味します。

最も明白な解決策は、ストリームをファイルに変換し、そのファイルを処理することです。しかし、質問はストリームのある種のフィルターを要求します。追加ファイルの使用についてではありません。

変数

素朴な解決策は、入力全体を変数に取り込むことです。

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 with -zオプション:

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

Awk(任意のawk)を使用して、ストリーム全体を丸呑みにし、末尾の改行なしでprintfストリームを丸めます。

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

ファイル全体をメモリにロードすることは良い考えではないかもしれません、それは多くのメモリを消費するかもしれません。

メモリ内の2行

Awkでは、前の行を変数に格納して現在の行を出力することにより、ループごとに2行を処理できます。

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

直接処理

しかし、私たちはもっとうまくやることができました。

改行なしで現在の行を印刷し、次の行が存在する場合にのみ改行を印刷する場合、一度に1行ずつ処理し、最後の行はnotの後に改行があります。

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
0
Isaac