コードをより多くの行で機能させる方法がわかりません。
これは元のファイルt.txtです。
Hello Earth
Hello Mars
しかし、私は次の出力を取得します:
Mars Hello Earth Hello
私の予想される出力はこれです:
Earth Hello
Mars Hello
一般的に、行の順序は同じにしたいが、単語を逆にしたい。一般的なケースでは、入力は次のようになります。
one two
four five
期待される出力はこれです:
two one
five four
私のコードは次のとおりです:
#!/bin/bash
text=$(cat $1)
arr=($text)
al=${#arr[@]}
let al="al-1"
while (($al >= 0))
do
echo -n "${arr[al]}"
echo -n " "
let al="al - 1"
done
echo
以下に示すすべての例は、行に任意の数の単語がある一般的なケースで機能します。基本的な考え方はどこでも同じです。ファイルを1行ずつ読み取り、単語を逆に印刷する必要があります。 AWKは、テキスト処理に必要なすべてのツールをプログラムで実行済みであり、移植性が最も高いため、これを最高に促進します。どのawk派生物でも使用でき、ほとんどのシステムで使用できます。 Pythonには、文字列処理のためのかなりの数の優れたユーティリティもあり、私たちの仕事を可能にします。これは、より現代的なシステム向けのツールだと思います。Bash、IMHOは、最も望ましくないアプローチです。 、移植性、潜在的な危険、および実行する必要がある「トリッキー」の量のため。
_$ awk '{for(i=NF;i>=1;i--) printf "%s ", $i;print ""}' input.txt
Earth Hello
Mars Hello
_
これが機能する方法はかなり単純です。行の各Wordを逆方向にループし、スペースで区切られた単語を出力します。これは、_printf "%s ",$i
_関数(フォーマットされた文字列を出力する)とforループによって実行されます。 NF
変数はフィールドの数に対応します。デフォルトのフィールド区切り文字はスペースと見なされます。まず、スローアウェイ変数i
を単語数に設定し、反復ごとに変数をデクリメントします。したがって、行に3つの単語がある場合、フィールド$ 3、次に$ 2、および$ 1を印刷します。最後のパスの後、変数iは0になり、条件_i>=1
_はfalseになり、ループが終了します。行がつなぎ合わされるのを防ぐために、_print ""
_を使用して改行を挿入します。この場合、AWKコードブロック_{}
_は行ごとに処理されます(コードブロックの前に一致条件がある場合、実行されるコードブロックの一致に依存します)。
代替ソリューションが好きな人のために、ここにPythonがあります:
_$ python -c "import sys;print '\n'.join([ ' '.join(line.split()[::-1]) for line in sys.stdin ])" < input.txt
Earth Hello
Mars Hello
_
ここの考え方は少し異なります。 _<
_演算子は、現在のシェルに_input.txt
_をpythonのstdin
ストリームにリダイレクトするように指示し、その行を1行ずつ読み取ります。ここでは、リスト内包表記を使用して行のリストを作成します。これが[ ' '.join(line.split()[::-1]) for line in sys.stdin ]
パーツが行うことです。 ' '.join(line.split()[::-1])
の部分は、行を取り、それを単語のリストに分割し、_[::-1]
_を介してリストを逆にし、次に' '.join()
がスペースで区切られた文字列を作成します。結果として、より大きな文字列のリストができました。最後に、'\n'.join()
はさらに大きな文字列を作成し、各項目は改行で結合されます。
つまり、この方法は基本的に「分割して再構築する」アプローチです。
_#!/bin/bash
while IFS= read -r line
do
bash -c 'i=$#; while [ $i -gt 0 ];do printf "%s " ${!i}; i=$(($i-1)); done' sh $line
echo
done < input.txt
_
そしてテスト実行:
_$ ./reverse_words.sh
Earth Hello
Mars Hello
_
Bash自体には強力なテキスト処理機能がありません。ここで何が起こるかというと、
_while IFS= read -r line
do
# some code
done < text.txt
_
これは頻繁に使用される手法であり、コマンドまたはテキストファイルの出力を行ごとに読み取るシェルスクリプトで広く使用されています。各行は_$line
_変数に格納されます。
内側には
_bash -c 'i=$#; while [ $i -gt 0 ];do printf "%s " ${!i}; i=$(($i-1)); done' sh $line
_
ここでは、bash
と_-c
_フラグを使用して、単一引用符で囲まれた一連のコマンドを実行します。 _-c
_を使用すると、bash
は、コマンドライン引数を_$0
_で始まる変数に割り当て始めます。その_$0
_は、プログラムの名前を示すために伝統的に使用されているため、最初にsh
ダミー変数を使用しています。
引用符で囲まれていない_$line
_は、単語分割と呼ばれる動作により、個々の項目に分割されます。シェルスクリプトでは、単語の分割は望ましくないことが多く、「$ foo」のように常に変数を引用する」という声がよく聞かれます。ただし、この場合、単語分割は単純なテキストの処理に適しています。テキストに_$var
_のようなものが含まれていると、このアプローチがうまく機能しない可能性があります。これと他のいくつかの理由により、pythonとawkアプローチの方が優れていると思います。
内部コードについても、簡単です。引用符で囲まれていない_$line
_は単語に分割され、内部コードに渡されて処理されます。引数の数_$#
_を受け取り、それをスローアウェイ変数i
に格納し、再び-変数の間接参照と呼ばれるものを使用して各項目を出力します-これは_${!i}
_の部分です(注これはバシズムです-他のシェルでは利用できません)。また、_printf "%s "
_を使用して、スペースで区切られた各Wordを出力します。それが完了すると、echo
は改行を追加します。
基本的に、このアプローチはawkとpythonの両方を組み合わせたものです。ファイルを1行ずつ読み取りますが、bash
のいくつかの機能を使用してジョブを実行し、各行を分割して征服します。
より簡単なバリエーションは、GNU tac
コマンドで実行でき、Word分割で再び再生できます。tac
は、入力ストリームまたはファイルの行を反転するために使用されます。しかし、この場合は_-s " "
_を指定してスペースをセパレーターとして使用します。したがって、var
には改行で区切られた単語のリストが逆の順序で含まれますが、_$var
_が引用されていないため、改行はスペースで置き換えられます。
_#!/bin/bash
while IFS= read -r line
do
var=$(tac -s " " <<< "$line" )
echo $var
done < input.txt
_
そして、これは任意の入力行を持つ3つのメソッドです
_$ cat input.txt
Hello Earth end of line
Hello Mars another end of line
abra cadabra magic
$ ./reverse_words.sh
line of end Earth Hello
line of end another Mars Hello
magic cadabra abra
$ python -c "import sys;print '\n'.join([ ' '.join(line.split()[::-1]) for line in sys.stdin ])" < input.txt
line of end Earth Hello
line of end another Mars Hello
magic cadabra abra
$ awk '{for(i=NF;i>=1;i--) printf "%s ", $i;print ""}' input.txt
line of end Earth Hello
line of end another Mars Hello
magic cadabra abra
_
pythonの場合と同じです。各行を単語の配列に分割し、配列を逆にして出力します。
_$ Perl -lane '@r=reverse(@F); print "@r"' input.txt
line of end Earth Hello
line of end another Mars Hello
magic cadabra abra
$ Ruby -ne 'puts $_.chomp.split().reverse.join(" ")' < input.txt
line of end Earth Hello
line of end another Mars Hello
magic cadabra abra
_
awk
で単語を入れ替えるだけです:
awk '{print $2, $1}'
例:
% cat bar.txt
Hello Earth
Hello Mars
% awk '{print $2, $1}' bar.txt
Earth Hello
Mars Hello
sed
ソリューション次のGNU sed
プログラムは、ループを使用して(最初から開始して)各Wordを行末に移動します。詳細は、コードとしてコメントとして挿入されます。
sed -r '
# Mark the current end of the line by appending a LF character ("\n")
G
# Main loop: move the first Word of the line just after the LF
# and repeat until the LF is at the beginning of the line
:loop
s/([^[:space:]]+)(.*\n)/\2\1 /
t loop
# Remove remaining spaces up to the LF and the superfluous trailing space
s/.*\n| $//g
'
書き込み専用バージョン:
sed -r 'G; :loop; s/(\S+)(.*\n)/\2\1 /; t loop; s/.*\n| $//g'
テスト:
$ sed -r '...' <<< "The quick
brown fox jumps
over
the lazy dog"
...利回り:
quick The
jumps fox brown
over
dog lazy the
移植可能(POSIXly):
sed '
G
:loop
s/\([^[:space:]]\{1,\}\)\(.*\n\)/\2\1 /
t loop
s/ $//
s/.*\n//'