より多くの行に対して私のコードを操作する方法がわかりません。
元のファイル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
以下に示す例はすべて、1行の単語数に関係なく一般的な場合に適用されます。基本的な考えはどこでも同じです。ファイルを1行ずつ読み、単語を逆に印刷する必要があります。 AWKは、プログラムでテキスト処理を実行するために必要なすべてのツールをすでに備えており、最も移植性が高いため、これを最も促進します。すべてのawk派生と組み合わせて使用でき、ほとんどのシステムにあります。 Pythonには、作業を完了するのに役立つ優れた文字列操作ユーティリティがたくさんあります。私はこれがより現代的なシステムのためのツールであると言いたいと思います。 IMHO、Bashは、移植性、潜在的なリスク、および実行する必要がある「トリック」操作の量による最も理想的なアプローチです。
AWK
$ awk '{for(i=NF;i>=1;i--) printf "%s ", $i;print ""}' input.txt
Earth Hello
Mars Hello
仕組みはとても簡単です。行の各単語を後ろに繰り返して、スペースで区切られた単語を印刷します。これはprintf "%s ",$i
、フォーマットされた文字列を印刷する関数とforループを使用して行われます。NF
変数はフィールド数に対応します。デフォルトのフィールド区切り文字はスペースと見なされます。まず、ワンタイム変数をi
単語数に設定し、各反復ごとにその変数を減らします。したがって、1行に3つの単語がある場合は、$ 3フィールド、$ 2、および$ 1フィールドを印刷します。最後のパスの後、変数iは0になり、条件はi>=1
falseになり、ループが終了します。行が互いにつながるのを防ぐために、挿入された改行文字を使用してくださいprint ""
。この場合、AWKコードブロックは{}
各行で処理されます(コードブロックの前に一致条件がある場合は一致するかどうかによって異なります)。
Python
代替ソリューションを好む人のために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行を取り、リストを反転して単語リストに分割し、スペースで区切られた[::-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自体には強力なテキスト処理機能はありません。ここで何が起こるのかは、ファイルを1行ずつ読み込むことです。
while IFS= read -r line
do
# some code
done < text.txt
これは、コマンドまたはテキストファイルの出力を1行ずつ読み取るためにシェルスクリプトで広く使用されている一般的な技術です。各行は$line
変数に保存されます。
中に私たちがいます。
bash -c 'i=$#; while [ $i -gt 0 ];do printf "%s " ${!i}; i=$(($i-1)); done' sh $line
ここでは、一重引用符で囲まれた一連のコマンドを実行するためにbash
withフラグを使用します。使用されると、で始まる変数にコマンドライン引数を割り当て始めます。伝統的にプログラム名を表すために使用されるので、まずダミー変数を使用します。-c
-c
bash
$0
$0
sh
引用されていないコンテンツは、$line
トークン化と呼ばれる動作のために別々の項目に分けられます。シェルスクリプトでは通常、トークン化は望ましくなく、「$foo」などの変数を常に引用することをよく聞きます。ただし、この場合、トークン化は単純なテキストを処理するのに役立ちます。テキストにこのような内容が含まれていると、$var
このアプローチが壊れる可能性があります。この理由と他の多くの理由で、私はPythonとawkのアプローチがより良いと思います。
内部コードに関しても簡単です。引用符で囲まれていない部分を$line
単語に分割し、処理のために内部コードに渡します。引数の数を取得し$#
、それをスローされた変数に保存しi
、変数間接参照と呼ばれるものを使用して各項目を再印刷します。これはまさにその部分です${!i}
(これはbashismです。他のシェルでは使用できません)。今回もprintf "%s "
各単語をスペースで区切って印刷します。完了すると、echo
改行が追加されます。
デフォルトでは、このアプローチはawkとPythonを混在させることです。ファイルを1行ずつ読みますが、bash
これらの機能を使用して各行を分割して征服します。
tac
GNUコマンドを使用して、ワードセパレータを使用してより簡単なバリエーションを実行できます。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
答え2
単語を変更してくださいawk
。
awk '{print $2, $1}'
例:
% cat bar.txt
Hello Earth
Hello Mars
% awk '{print $2, $1}' bar.txt
Earth Hello
Mars Hello
答え3
強制sed
解決
次のGNUsed
プログラムは、ループを使用して各単語を行末(最初の単語から始まる)に移動します。詳細はコードにコメントとして挿入されます。
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//'
答え4
rev
文字と行はありますがtac
(わかる限り)テキストはありません。これは私にとって最も簡単なBashイディオムです。
while read line; do echo $(echo $line | tr " " "\n" | tac); done < $1