web-dev-qa-db-ja.com

ファイルの行にあるすべての単語を逆順に印刷する

コードをより多くの行で機能させる方法がわかりません。

これは元のファイル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
8
Bersekz

以下に示すすべての例は、行に任意の数の単語がある一般的なケースで機能します。基本的な考え方はどこでも同じです。ファイルを1行ずつ読み取り、単語を逆に印刷する必要があります。 AWKは、テキスト処理に必要なすべてのツールをプログラムで実行済みであり、移植性が最も高いため、これを最高に促進します。どのawk派生物でも使用でき、ほとんどのシステムで使用できます。 Pythonには、文字列処理のためのかなりの数の優れたユーティリティもあり、私たちの仕事を可能にします。これは、より現代的なシステム向けのツールだと思います。Bash、IMHOは、最も望ましくないアプローチです。 、移植性、潜在的な危険、および実行する必要がある「トリッキー」の量のため。

AWK

_$ 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があります:

_$ 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()はさらに大きな文字列を作成し、各項目は改行で結合されます。

つまり、この方法は基本的に「分割して再構築する」アプローチです。

BASH

_#!/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 
_

追加:PerlとRuby

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
_
15

awkで単語を入れ替えるだけです:

awk '{print $2, $1}'

例:

% cat bar.txt
Hello Earth
Hello Mars

% awk '{print $2, $1}' bar.txt
Earth Hello
Mars Hello
10
heemayl

必須の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//'
3
xhienne