強制sed解決

強制sed解決

より多くの行に対して私のコードを操作する方法がわかりません。

元のファイル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>=1falseになり、ループが終了します。行が互いにつながるのを防ぐために、挿入された改行文字を使用してください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.txtPythonの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

ここでは、一重引用符で囲まれた一連のコマンドを実行するためにbashwithフラグを使用します。使用されると、で始まる変数にコマンドライン引数を割り当て始めます。伝統的にプログラム名を表すために使用されるので、まずダミー変数を使用します。-c-cbash$0$0sh

引用されていないコンテンツは、$lineトークン化と呼ばれる動作のために別々の項目に分けられます。シェルスクリプトでは通常、トークン化は望ましくなく、「$foo」などの変数を常に引用することをよく聞きます。ただし、この場合、トークン化は単純なテキストを処理するのに役立ちます。テキストにこのような内容が含まれていると、$varこのアプローチが壊れる可能性があります。この理由と他の多くの理由で、私はPythonとawkのアプローチがより良いと思います。

内部コードに関しても簡単です。引用符で囲まれていない部分を$line単語に分割し、処理のために内部コードに渡します。引数の数を取得し$#、それをスローされた変数に保存しi、変数間接参照と呼ばれるものを使用して各項目を再印刷します。これはまさにその部分です${!i} (これはbashismです。他のシェルでは使用できません)。今回もprintf "%s "各単語をスペースで区切って印刷します。完了すると、echo改行が追加されます。

デフォルトでは、このアプローチはawkとPythonを混在させることです。ファイルを1行ずつ読みますが、bashこれらの機能を使用して各行を分割して征服します。

tacGNUコマンドを使用して、ワードセパレータを使用してより簡単なバリエーションを実行できます。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

関連情報