Sed/awk/perl: カンマ区切り値の順序を逆にし、他のテキストを保持する

Sed/awk/perl: カンマ区切り値の順序を逆にし、他のテキストを保持する

次のようなテキストがあります:

LABEL1
    .BYTE 01, 02, 03, 04, 05
    .BYTE 01, 02, 03

カンマ区切り値の順序のみを逆にする必要があります:

LABEL1
    .BYTE 05, 04, 03, 02, 01
    .BYTE 03, 02, 01

これは次のような場合に機能する必要があります:

ITINERARY_ARRAY_01
    .BYTE <ITINERARY_00A
    .BYTE <ITINERARY_01A
    .BYTE <ITINERARY_02A
    .BYTE <ITINERARY_03A
    .BYTE <ITINERARY_04A
    .BYTE <ITINERARY_05A
    .BYTE <ITINERARY_06A
    .BYTE <ITINERARY_07A
    .BYTE <ITINERARY_08A
    .BYTE <ITINERARY_09A
    .BYTE <ITINERARY_10A
    .BYTE <ITINERARY_11A
    .BYTE <ITINERARY_12A
    .BYTE <ITINERARY_13A
    .BYTE <ITINERARY_14A
;-------------------
ITINERARY_01E
    .BYTE $03, $05, $07, $00
;-------------------
ITINERARY_01F
    .BYTE $03, $05, $07, $09, $00
;-------------------
ITINERARY_01G
    .BYTE $28, $0D, $00
;-------------------
ITINERARY_01H
    .BYTE $28, $0D, $0F, $13, $00
;-------------------
ITINERARY_01I
    .BYTE $28, $0D, $0F, $11, $00
;-------------------
ITINERARY_01J
    .BYTE $03, $05, $07, $09, $20, $1E, $00
;-------------------
ITINERARY_01K
    .BYTE $28, $0D, $0F, $13, $15, $00
;-------------------
ITINERARY_01L
    .BYTE $03, $05, $07, $09, $20, $1E, $1C, $27
    .BYTE $00
;---------------------

ここでは、".BYTE" の後の値は逆順、16 進形式、プレフィックスとして "$" を使用して変更する必要はありません... この "編集" については申し訳ありませんが、今になって初めて気づきました。 ありがとうございました!

答え1

ここでは次のように行いますsed:

sed '/,/!b                                                   
s/\( *[^ ]*\)\(.*\)/\2,\n\1/;:t
s/\([^,]*,\)\(\n.*\)/\2\1/;tt
s/\n\(.*\),/\1/' <<\DATA
LABEL1
    .BYTE 01, 02, 03, 04, 05
    .BYTE 01, 02, 03        
LABEL1
    .BYTE 01, 02, 03, 04, 05
    .BYTE 01, 02, 03
DATA

出力

LABEL1
    .BYTE 05, 04, 03, 02, 01 
    .BYTE 03, 02, 01 
LABEL1
    .BYTE 05, 04, 03, 02, 01 
    .BYTE 03, 02, 01 

現在の行にカンマがあるかどうかをチェックします。!カンマがない場合はsed bスクリプトを終了し、その行を自動印刷します。するカンマが含まれていると、sed次のようになります。

  1. まず、次のものを代入して行を準備しますs///
    • \( *[^ ]*\)- 最初に出現する 0 個以上のスペースのシーケンスの直後に、参照される 0 個以上のスペース以外の文字のシーケンスが続き、その\1直後に... が続きます。
    • \(.*\)- 行上のその他すべては\2...として参照されます。
    • ...と\2,\n\1
    • 注記-このように\n右側のs///置換フィールドでエスケープを使用すると、完全には移植できません。これをサポートしていない の場合は、代わりにステートメント内sedの をリテラルの改行に置き換えることで実行できます。n
  2. :と呼ばれるブランチ/テスト ラベルを定義しますt
  3. まだ可能ですが、sed s///代替品:
    • \([^,]*,\)- 0個以上のコンマ以外の文字のシーケンスそれから1 つのカンマが参照され、その\1直後に... が続きます。
    • \(\n.*\)\n- 少なくとも 1 つのewline 文字で始まり、その後にパターン スペース内に残っているすべてのものが続くシーケンス( \2...として参照されます)
    • ...と\2\1
  4. 前のs///置換がt成功した場合は、 est ラベルsedに戻って:t再試行します。
  5. 最後にsed少しクリーンアップして置き換えます:
    • \n\(.*\),- 最初に出現する\n改行文字と最後に出現するコンマ...
    • \1- ...その間にあるすべてのものとともに。

sed再帰的置換と同様に、 \newline デリミタは、一度に 1 つのコンマ区切りフィールドを逆方向に処理します。ewline が\n行の最初の文字になると、置換が停止します。l再帰的置換プロセスの進行状況を以下に示します。

 01, 02, 03, 04, 05,\n    .BYTE$
 01, 02, 03, 04,\n    .BYTE 05,$
 01, 02, 03,\n    .BYTE 05, 04,$
 01, 02,\n    .BYTE 05, 04, 03,$
 01,\n    .BYTE 05, 04, 03, 02,$
\n    .BYTE 05, 04, 03, 02, 01,$

最初の準備的な置換の後は、sedカンマと挿入された改行文字以外の文字で区切られません\nどれでもカンマ区切りの値のようなものでも問題なく動作します。長いビットを実行した結果の出力は次のとおりです。

ITINERARY_ARRAY_01
    .BYTE <ITINERARY_00A
    .BYTE <ITINERARY_01A
    .BYTE <ITINERARY_02A
    .BYTE <ITINERARY_03A
    .BYTE <ITINERARY_04A
    .BYTE <ITINERARY_05A
    .BYTE <ITINERARY_06A
    .BYTE <ITINERARY_07A
    .BYTE <ITINERARY_08A
    .BYTE <ITINERARY_09A
    .BYTE <ITINERARY_10A
    .BYTE <ITINERARY_11A
    .BYTE <ITINERARY_12A
    .BYTE <ITINERARY_13A
    .BYTE <ITINERARY_14A
;-------------------
ITINERARY_01E
    .BYTE $00, $07, $05, $03 
;-------------------
ITINERARY_01F
    .BYTE $00, $09, $07, $05, $03 
;-------------------
ITINERARY_01G
    .BYTE $00, $0D, $28 
;-------------------
ITINERARY_01H
    .BYTE $00, $13, $0F, $0D, $28 
;-------------------
ITINERARY_01I
    .BYTE $00, $11, $0F, $0D, $28 
;-------------------
ITINERARY_01J
    .BYTE $00, $1E, $20, $09, $07, $05, $03 
;-------------------
ITINERARY_01K
    .BYTE $00, $15, $13, $0F, $0D, $28 
;-------------------
ITINERARY_01L
    .BYTE $27, $1C, $1E, $20, $09, $07, $05, $03
    .BYTE $00
;---------------------

答え2

ファイルrevbytes2.awk

#!/usr/bin/awk -f
BEGIN {
        FS=",? +"
}
NF>2 && match($0,"^ +\.BYTE ") {
        printf substr($0,1,RSTART+RLENGTH-1)
        for(i=NF;i>3;i--) printf $i", "
        print $3
        next
}
1

FS=",? +"awkバイトの後ろのスペース.BYTE,バイト間のプラススペースシーケンスをフィールド区切り文字として認識します。

各行について、スペースで始まり、その後に.BYTE1 つのスペースが続く 2 つ以上のフィールドを持つ行が検索され、このプレフィックスの開始と長さが式の副作用としてRSTART記憶されます。RLENGTHmatch(...)

この一致が見つかり、フィールドが 2 つ以上ある場合は、およびを使用して元の行からプレフィックスが切り取られRSTARTRLENGTH残りのフィールドが逆の順序で印刷されます。

スペースと.BYTEスペースのプレフィックスが見つからない場合、またはフィールドが 2 つ以下である場合、行はそのまま印刷されます。したがって、.BYTE逆転するものがないため、1 バイトのみを定義する行に対してもこれが実行されます。

テスト走行:

$ diff -u$(wc -l <input) input <(awk -f revbytes2.awk input)
--- input       2014-10-19 06:04:48.280714146 +0200
+++ /dev/fd/63  2014-10-19 22:40:01.385538235 +0200
@@ -1,42 +1,42 @@
 ITINERARY_ARRAY_01
     .BYTE <ITINERARY_00A
     .BYTE <ITINERARY_01A
     .BYTE <ITINERARY_02A
     .BYTE <ITINERARY_03A
     .BYTE <ITINERARY_04A
     .BYTE <ITINERARY_05A
     .BYTE <ITINERARY_06A
     .BYTE <ITINERARY_07A
     .BYTE <ITINERARY_08A
     .BYTE <ITINERARY_09A
     .BYTE <ITINERARY_10A
     .BYTE <ITINERARY_11A
     .BYTE <ITINERARY_12A
     .BYTE <ITINERARY_13A
     .BYTE <ITINERARY_14A
 ;-------------------
 ITINERARY_01E
-    .BYTE $03, $05, $07, $00
+    .BYTE $00, $07, $05, $03
 ;-------------------
 ITINERARY_01F
-    .BYTE $03, $05, $07, $09, $00
+    .BYTE $00, $09, $07, $05, $03
 ;-------------------
 ITINERARY_01G
-    .BYTE $28, $0D, $00
+    .BYTE $00, $0D, $28
 ;-------------------
 ITINERARY_01H
-    .BYTE $28, $0D, $0F, $13, $00
+    .BYTE $00, $13, $0F, $0D, $28
 ;-------------------
 ITINERARY_01I
-    .BYTE $28, $0D, $0F, $11, $00
+    .BYTE $00, $11, $0F, $0D, $28
 ;-------------------
 ITINERARY_01J
-    .BYTE $03, $05, $07, $09, $20, $1E, $00
+    .BYTE $00, $1E, $20, $09, $07, $05, $03
 ;-------------------
 ITINERARY_01K
-    .BYTE $28, $0D, $0F, $13, $15, $00
+    .BYTE $00, $15, $13, $0F, $0D, $28
 ;-------------------
 ITINERARY_01L
-    .BYTE $03, $05, $07, $09, $20, $1E, $1C, $27
+    .BYTE $27, $1C, $1E, $20, $09, $07, $05, $03
     .BYTE $00
 ;---------------------

比較mawkgawk出力:

$ diff <(mawk -f revbytes2.awk input) <(gawk -f revbytes2.awk input)
gawk: revbytes2.awk:5: warning: escape sequence `\.' treated as plain `.'

明らかに、stdout には違いはありません。よかった!

式の中に"^ +\056BYTE "の代わりにを記述すると、警告は消えます。"^ +\.BYTE "match(...)

頻繁に使用している人は、gawk警告を回避するより良い方法を知っているかもしれません。

答え3

私ならこうします:

perl -MTie::File -e'
    tie @lines,"Tie::File","your_file";
    for(@lines){
        next unless /,/; # Skip lines with no commas
        $csv = /(\s*[^,\s]+,.*)/;
        $new_csv = join ",",reverse split /,/,$csv;
        s/\Q$csv/$new_csv/;
    }'

免責事項!!

これによりファイルが変更されます現地で必要ない場合は、ファイルのダミーコピーを使用します。

元のファイルを変更しないバージョン

perl -pe'
        next unless /,/; # Skip lines with no commas
        chomp;
        $csv = /(\s*[^,\s]+,.*)/;
        $new_csv = join ",",reverse split /,/,$csv;
        $new_csv .= "\n"; # The newline removed by chomp
        s/\Q$csv/$new_csv/;
    ' your_file

仮定

  • カンマの周りのスペースは気にする必要はありません。
  • 最初の CSV 値は、.BYTE少なくとも 1 つのスペースでオフセットされます。
  • 「順序を逆にする」とは、数値の降順で並べ替えるのではなく、ファイル内で見つかった順序を逆にすることを意味します。

入力

ITINERARY_ARRAY_01
    .BYTE <ITINERARY_00A
    .BYTE <ITINERARY_01A
    .BYTE <ITINERARY_02A
    .BYTE <ITINERARY_03A
    .BYTE <ITINERARY_04A
    .BYTE <ITINERARY_05A
    .BYTE <ITINERARY_06A
    .BYTE <ITINERARY_07A
    .BYTE <ITINERARY_08A
    .BYTE <ITINERARY_09A
    .BYTE <ITINERARY_10A
    .BYTE <ITINERARY_11A
    .BYTE <ITINERARY_12A
    .BYTE <ITINERARY_13A
    .BYTE <ITINERARY_14A
;-------------------
ITINERARY_01E
    .BYTE $03, $05, $07, $00
;-------------------
ITINERARY_01F
    .BYTE $03, $05, $07, $09, $00
;-------------------
ITINERARY_01G
    .BYTE $28, $0D, $00
;-------------------
ITINERARY_01H
    .BYTE $28, $0D, $0F, $13, $00
;-------------------
ITINERARY_01I
    .BYTE $28, $0D, $0F, $11, $00
;-------------------
ITINERARY_01J
    .BYTE $03, $05, $07, $09, $20, $1E, $00
;-------------------
ITINERARY_01K
    .BYTE $28, $0D, $0F, $13, $15, $00
;-------------------
ITINERARY_01L
    .BYTE $03, $05, $07, $09, $20, $1E, $1C, $27
    .BYTE $00
;---------------------

出力

ITINERARY_ARRAY_01
    .BYTE <ITINERARY_00A
    .BYTE <ITINERARY_01A
    .BYTE <ITINERARY_02A
    .BYTE <ITINERARY_03A
    .BYTE <ITINERARY_04A
    .BYTE <ITINERARY_05A
    .BYTE <ITINERARY_06A
    .BYTE <ITINERARY_07A
    .BYTE <ITINERARY_08A
    .BYTE <ITINERARY_09A
    .BYTE <ITINERARY_10A
    .BYTE <ITINERARY_11A
    .BYTE <ITINERARY_12A
    .BYTE <ITINERARY_13A
    .BYTE <ITINERARY_14A
;-------------------
ITINERARY_01E
    .BYTE $00, $07, $05, $03
;-------------------
ITINERARY_01F
    .BYTE $00, $09, $07, $05, $03
;-------------------
ITINERARY_01G
    .BYTE $00, $0D, $28
;-------------------
ITINERARY_01H
    .BYTE $00, $13, $0F, $0D, $28
;-------------------
ITINERARY_01I
    .BYTE $00, $11, $0F, $0D, $28
;-------------------
ITINERARY_01J
    .BYTE $00, $1E, $20, $09, $07, $05, $03
;-------------------
ITINERARY_01K
    .BYTE $00, $15, $13, $0F, $0D, $28
;-------------------
ITINERARY_01L
    .BYTE $27, $1C, $1E, $20, $09, $07, $05, $03
    .BYTE $00
;---------------------

答え4

入力すると、以下を使用できますperl:

$ perl -MText::Tabs -anle '
    BEGIN {$tabstop = 4};
    print and next if /^\S/;
    @nums = grep { $_ =~ /\d+/ } @F;
    map { s/\D//g } @nums;
    map { $_ = (pop @nums) . (@nums==0 ? "" : ",")
        if $_ =~ /\d+/ } @F;
    print expand "\t@F";
' file
LABEL1
    .BYTE 05, 04, 03, 02, 01
    .BYTE 03, 02, 01

@nums = sort { $a <=> $b } grep { $_ =~ /\d+/ } @F;元の入力はソートされていると想定しています。そうでない場合は、の代わりにを使用できます@nums = grep { $_ =~ /\d+/ } @F;

関連情報