1. 概要

awk コマンドは、非常に強力なテキスト処理ツールです。 これを使用して、Linuxコマンドラインでさまざまなテキスト処理の問題を解決できます。

このチュートリアルでは、awkスクリプトから外部プログラムを呼び出す方法を見ていきます。

2. awkから外部コマンドを呼び出す

awk は強力なユーティリティですが、いくつかの問題を解決するために外部コマンドの支援が必要になる場合があります。

例えば:

  • awk + sendmail:メールアドレスとメッセージを含むCSVファイルを読み取り、各メッセージを処理して送信します
  • awk + cp:ファイルリストの入力を読み取り、定義された名前パターンで必要な宛先にファイルをコピーします
  • awk + md5sum:ファイル名のリストを含む入力を読み取り、ファイル名とファイルのMD5ハッシュを出力します

awk から外部コマンドを呼び出す場合、要件に応じて、通常、返されたステータスまたはコマンドの出力のいずれかを取得する必要があります。

たとえば、上記の awk + cp の例では、 cp コマンドの戻りステータスを取得して、コピーが正常に完了したかどうかを確認します。 awk + md5sum の例では、スクリプトが正しい出力を生成できるように、md5sumコマンドの出力が必要です。

後のセクションでは、例を通じてこれら2つのケースを処理する方法を説明します。

3. 外部コマンドの実行ステータスを取得する

ファイルのバックアップの問題を解決することにより、外部コマンドの返されたステータスを取得する方法について説明します。 入力ファイルを見てみましょう。

$ cat /tmp/test/file_list.csv 
Id,Filename,Create_date
1,"/tmp/test/source/file1.txt",2020-06-01
2,"/tmp/test/source/file2.pdf",2020-05-01
3,"/tmp/test/source/file3.txt",2020-06-03
4,"/tmp/test/source/file4.zip",2020-06-07

file_list.csv という名前が示すように、これはCSVファイルです。 2番目のフィールドにファイル名が含まれています。 このファイルによると、 / tmp / test /sourceディレクトリの下にファイルがあります。

$ ls -l /tmp/test/source
-rw-r--r-- 1 kent kent   30 Jun  7 00:13 file1.txt
-rw-r--r-- 1 kent kent   36 Jun  7 00:13 file2.pdf
-rwx------ 1 root root 1752 Jun  7 00:10 file3.txt
-rw-r--r-- 1 kent kent   37 Jun  7 00:13 file4.zip

要件は、2番目のフィールドのファイルを / tmp / test / backup にコピーし、CSVファイルに「 Backup_status 」という新しいフィールドを追加して、対応するファイルのバックアップステータスは、「成功」または「失敗」です。

私たちが読んだ場合 ls 上記の出力を注意深く行うと、 file3.txt 許可があります 700。 通常のユーザーが読み込もうとすると、「許可が拒否されました」というエラーが表示されます。 したがって、file3.txtのバックアップステータスは出力で「Failed」になると予想されます。

awkのsystem(cmd)関数は、外部コマンドを呼び出して終了ステータスを取得できます。 この機能は、問題を解決するための鍵です。

まず、問題がどのように解決されるかを見てみましょう。

kent$ awk -F',' -v OFS=',' -v toDir="/tmp/test/backup"     \
        'NR==1{print $0,"Backup_status"; next}
        { backup_cmd = "cp " $2 " " toDir " >/dev/null 2>&1"
          st = system(backup_cmd)
          print $0, ( st==0? "Success" : "Failed" ) }' /tmp/test/file_list.csv
Id,Filename,Create_date,Backup_status
1,"/tmp/test/source/file1.txt",2020-06-01,Success
2,"/tmp/test/source/file2.pdf",2020-05-01,Success
3,"/tmp/test/source/file3.txt",2020-06-03,Failed
4,"/tmp/test/source/file4.zip",2020-06-07,Success

awk コマンドを実行した後、バックアップステータスが「Success」のすべてのファイルが予期されたディレクトリにコピーされました。

$ ls /tmp/test/backup 
file1.txt  file2.pdf  file4.zip

それでは、 awk コードを1行ずつ見ていき、どのように機能するかを理解しましょう。

  • 1行目:通常のユーザーkentによってawkコマンドを開始し、FSOFSなどの必要な変数を設定します。
  • 2行目:新しいフィールド Backup_status を追加してタイトルを拡張し、タイトルを印刷します
  • 3行目:stdoutstderrの両方を/dev / null にリダイレクトして、コピーコマンドを作成し、すべての出力を破棄します。
  • 4行目: system()関数を呼び出し、終了ステータスを変数stに保持します。
  • 5行目:バックアップステータス情報を出力に出力します( st ==0Successを意味します)

4. 外部コマンドの出力を取得する

外部プログラムを呼び出して、awkコードから終了ステータスを取得する方法を見てきました。 ただし、外部コマンドの出力を使用してさらに処理したい場合があります。

コマンドは、単一行の出力または複数行の出力を生成できます。 このセクションでは、両方のケースを処理する方法について説明します。

4.1. 外部コマンドから単一行出力を取得する

問題から始めましょう。

同じ入力ファイル/tmp/test/file_list.csvを再利用します。 今回は、CSVファイルに「 MIME_type 」という新しい列を追加して、各ファイルの MIMETypeを表示します。

MIMEタイプを取得するには、fileコマンドを使用できます。 たとえば、次の方法でファイル/tmp/test/source/file1.txtのMIMEタイプを取得できます。

$ file -b --mime-type file1.txt
text/plain

良い。 これまでのところ、問題を解決するために欠けているのは、ファイルコマンド を呼び出し、awkスクリプトから出力を取得する方法だけです。

awk 、と呼ばれる多機能コマンドがあります getline。 構築されたコマンドをパイプでつなぐことができます getline コマンドを実行し、コマンドの出力を次の構文で変数に保存します。

"an external command" | getline variable

たとえば、fileコマンドの出力をawk変数resultに保存する場合は、次のように記述できます。

"file -b --mime-type file1.txt" | getline result

それでは、物事を組み立てて、MIMEタイプの追加の問題を解決しましょう。

kent$ awk -F',' -v OFS=','                     \
        'NR==1{print $0,"MIME_type"; next}
        { cmd = "file -b --mime-type " $2
          cmd | getline result
          close(cmd)
          print $0, result }' /tmp/test/file_list.csv
Id,Filename,create_date,MIME_type
1,"/tmp/test/source/file1.txt",2020-06-01,text/plain
2,"/tmp/test/source/file2.pdf",2020-05-01,application/pdf
3,"/tmp/test/source/file3.txt",2020-06-03,regular file, no read permission
4,"/tmp/test/source/file4.zip",2020-06-07,application/zip

通常のユーザーkentでコマンドを実行したため、file3.txtfileコマンドを呼び出すとエラーメッセージが表示されました。 このエラーメッセージも出力に追加されます。

コマンドをgetlineコマンドにパイプして1行の出力を取得するのは非常に簡単です。

同じ方法を使用して複数行の出力を取得できますか? 次のセクションで調べてみましょう。

4.2. 外部コマンドから複数行の出力を取得する

一部のコマンドは、複数行の出力を生成する場合があります。 cmd|を使用して完全な出力を取得できるかどうか試してみましょう。 getline v アプローチ:

$ awk 'BEGIN{cmd="seq 10"; cmd | getline out; close(cmd); print out}'
1

おっとっと! コマンドseq10は10行の出力を生成することがわかっています。 ただし、awkは出力から最初の行のみをフェッチしました。 それの訳はこの形式のgetlineコマンドは、パイプから一度に1つのレコードを読み取ります。 

getlineコマンド自体に戻り値があります。 パイプからの出力がまだある場合は、1を返します。 それ以外の場合、getlineコマンドは0を返します。

$  awk 'BEGIN{cmd="seq 10";
        for(i=1;i<=11;i++) {
            retValue = cmd | getline out
            printf "getline returns: %s; cmd output: %s\n", retValue, retValue?out:"Null"                 
        }           
        close(cmd)}'
getline returns: 1; cmd output: 1
getline returns: 1; cmd output: 2
getline returns: 1; cmd output: 3
getline returns: 1; cmd output: 4
getline returns: 1; cmd output: 5
getline returns: 1; cmd output: 6
getline returns: 1; cmd output: 7
getline returns: 1; cmd output: 8
getline returns: 1; cmd output: 9
getline returns: 1; cmd output: 10
getline returns: 0; cmd output: Null

getlineを11回実行するループを作成します。 コマンドseq10は10行を出力することがわかっています。 したがって、10個の getlineリターンがあります:上記の出力では1

ただし、 cmd出力:10 が出力されると、パイプにはデータが含まれなくなります。 ここで、 getline コマンドを実行して、パイプからもう一度読み取ろうとすると、コマンドは0を返します。

したがって、 while ループを記述して、外部コマンドから完全な出力を取得できます。

$ awk 'BEGIN{cmd="seq 10";
       cmd_out=""
       while(cmd | getline step_out){
          cmd_out=cmd_out (cmd_out=="" ? "" : "\n") step_out
       }
       print cmd_out
       close(cmd)}'
1
2
3
4
5
6
7
8
9
10

4.3. close(cmd)を忘れないでください

を使用して外部コマンドの出力を取得する例を見てきました cmd | getline変数。

言及する価値があります cmd |を呼び出した後、パイプを閉じるにはclose(cmd)を呼び出す必要があります。 getline。 そうしないと、awkスクリプトが間違った結果を生成する可能性があります。

パイプを閉じないとどうなるか見てみましょう。 たとえば、テキストファイルがあるとします。

$ cat close_test.txt
"Awk is cool!"
"Sed is cool!"
"Awk is cool!"
"Sed is cool!"

ファイルでは、1行目と3行目が同じであり、2行目と最後の行も同じです。

次に、入力ファイルの各行に対して、外部の md5sum コマンドを呼び出して、各行にMD5ハッシュ値を追加します。 同一の行が同じMD5ハッシュ値を取得することを期待しています。 最初の試行では、 close(cmd)を呼び出してパイプを閉じません。

$ awk '{cmd="md5sum <<<"$0 ; cmd|getline md5; print $0,"MD5:" md5}' close_test.txt
"Awk is cool!" MD5:04cbd36582f5c11cce032ec44ec476d8  -
"Sed is cool!" MD5:f1844ba1dd262ecbbf798f7c38180693  -
"Awk is cool!" MD5:f1844ba1dd262ecbbf798f7c38180693  -
"Sed is cool!" MD5:f1844ba1dd262ecbbf798f7c38180693  -

出力は、最後の3行のMD5ハッシュ値が同じであることを示しています。 明らかに、これは間違った出力です。

なぜこれが起こったのかを簡単に説明しましょう。

パイプを閉じないと、同じ外部コマンドをgetlineにパイプするたびに、コマンドが再度実行されることはありません。 代わりに、最後の実行の出力から次のレコードを読み取ろうとします。 私たちは、 cmd | getline var コマンドは1または0を返します。 パイプにデータがなくなると、0が返され、var変数は設定されません。

各入力行のmd5変数をリセットし、 getline ステータスを出力して、問題をより簡単に理解しましょう。

$ awk '{md5=""; cmd="md5sum <<<"$0 
      status=cmd|getline md5; 
      print $0,"getline status:"status,"MD5:" md5}' close_test.txt
"Awk is cool!" getline status:1 MD5:04cbd36582f5c11cce032ec44ec476d8  -
"Sed is cool!" getline status:1 MD5:f1844ba1dd262ecbbf798f7c38180693  -
"Awk is cool!" getline status:0 MD5:
"Sed is cool!" getline status:0 MD5:

この問題の修正は、パイプからの出力を読み取った後、 close(cmd)を呼び出すだけです。

$ awk '{cmd="md5sum <<<"$0 ; cmd|getline md5;close(cmd); print $0,"MD5:" md5}' close_test.txt
"Awk is cool!" MD5:04cbd36582f5c11cce032ec44ec476d8  -       
"Sed is cool!" MD5:f1844ba1dd262ecbbf798f7c38180693  -
"Awk is cool!" MD5:04cbd36582f5c11cce032ec44ec476d8  -
"Sed is cool!" MD5:f1844ba1dd262ecbbf798f7c38180693  -

5. 結論

この記事では、awkを使用して外部プログラムを呼び出す方法を学びました。

要件に応じて、 system(cmd)を呼び出して外部コマンドの終了コードを取得するか、cmd|を使用して出力を取得できます。 getlineフォーム。

また、パイプを閉じるために close(cmd)を呼び出すことを忘れてはならない理由についても説明しました。