1. 概要

コマンドcatfile1file2を使用して複数のファイルを連結できることがわかっています。 ただし、2つのファイルを列ごとに結合したい場合があります。

このチュートリアルでは、Linuxコマンドラインでこれを行う方法を学習します。

2. 問題の紹介

2つのファイルを列ごとに結合したい場合があります。 つまり、コンテンツを読みやすく比較しやすいように、並べて表示します。

簡単な例を見てみましょう。

$ head left.txt right.txt 
==> left.txt <==
I am line 1 on the left.
I am line 2 on the left.
I am line 3 on the left.
I am line 4 on the left.

==> right.txt <==
Right side: line #1
Right side: line #2
Right side: line #3
Right side: line #4

上記の出力が示すように、left.txtright.txtの2つのファイルがあります。 ここで、次のような新しいファイルまたは出力が必要です。

I am line 1 on the left.	Right side: line #1
I am line 2 on the left.	Right side: line #2
I am line 3 on the left.	Right side: line #3
I am line 4 on the left.	Right side: line #4

この例は、最良のケースを示しています。 left.txtのすべての行の長さは同じです。 したがって、きれいに調整された出力を簡単に取得できます。

ただし、実際には、left.txtの行の長さはさまざまです。 出力をまだかなり調整したい場合があります。

また、2つのファイルの行数は異なる場合があります。 場合によっては、生成されたサイドバイサイドビューでファイルが最後に到達したかどうかを識別したいことがあります。

さらに、場合によっては、2つのファイルの内容の間のセパレーターをカスタマイズできればよいでしょう。

このチュートリアルでは、これらすべての側面をカバーするさまざまなソリューションについて説明します。

次に、それらの動作を見てみましょう。

3. 2つのファイルを並べて表示–貼り付けコマンド

貼り付けコマンドは、複数のファイルの行をマージできます。 また、使い方はとても簡単です。

$ paste left.txt right.txt 
I am line 1 on the left.	Right side: line #1
I am line 2 on the left.	Right side: line #2
I am line 3 on the left.	Right side: line #3
I am line 4 on the left.	Right side: line #4

ご覧のとおり、2つのファイルをpastコマンドに直感的に渡します。 入力ファイルの行をマージします。

pastコマンドのデフォルトの区切り文字はタブです。

この例では、 left.txt のすべての行の長さが同じであるため、2つのファイルの内容もタブで明確に区切られています。

4. 出力をきれいに揃える–コマンド

past コマンドを使用すると、2つのファイルを簡単に並べて表示できます。

ただし、前述したように、実際には、入力ファイルの形式はさまざまです。 たとえば、left.txtファイルの行の長さは異なる場合があります。 したがって、出力形式が乱雑になる可能性があります。

4.1. 新しい問題

別のファイルのペア、left2.txtright2.txtを見てみましょう。

$ cat -n left2.txt
     1	I'm line1.
     2	I am line2, I am very very very long.
     3	
     4	Hi, I'm line4.

$ cat right2.txt 
Line1 on the right
Line2 on the right
Line3 on the right
Line4 on the right
Line5 on the right
Line6 on the right

left2.txt ファイルには、4行あります。 また、3行目は空です。 は、-nオプションを指定したcatコマンドを使用して、出力に行番号を追加し、空の行を識別できるようにしました。

さらに、2行目は、ファイル内の他の行よりも長くなっています。

一方、right2.txtには同じ長さの6行があります。

ここで、2つのファイルを past コマンドに渡すと、次のようになります。

$ paste left2.txt right2.txt 
I'm line1.	Line1 on the right
I am line2, I am very very very long.	Line2 on the right
	Line3 on the right
Hi, I'm line4.	Line4 on the right
	Line5 on the right
	Line6 on the right

past コマンドは、2つのファイルの内容を並べて一覧表示します。 ただし、明らかに、形式は乱雑で読みにくいです。

次に、出力をきれいに調整しましょう。

4.2. コマンド

column コマンドは、 util-linux パッケージのメンバーであり、ほとんどの最新のLinuxディストリビューションでデフォルトで使用できます。

その名前が示すように、columnコマンドは、列の入力を再編成するのに適しています。 さらに、出力の配置とフォーマットを制御するための多くのオプションを提供します。

しかし、問題を解決するには、基本的に2つのオプションしか必要ありません。

  • -s –フィールド間のセパレーターの設定
  • -t にコンテンツをテーブルとして再フォーマットするように要求します。 つまり、列が整列されます

pastコマンドのデフォルトの区切り文字はタブであることがわかりました。 したがって、コマンドに、結果を再フォーマットするための区切り文字としてタブを使用するように指示できます。

$ paste left2.txt right2.txt | column -s $'\t' -t 
I'm line1.                             Line1 on the right
I am line2, I am very very very long.  Line2 on the right
                                       Line3 on the right
Hi, I'm line4.                         Line4 on the right
                                       Line5 on the right
                                       Line6 on the right

これで、出力がはるかに良く見えます。

タブを-sオプションに渡すと、 $’\t’という特別な形式を使用していることに気付く場合があります。

文字列‘\t’、を渡すと、columnコマンドはそれをタブとして扱いません。 代わりに、「t」文字を区切り文字として使用します。 つまり、 ANSI C標準の円記号でエスケープされた文字は、展開されません。

ただし、形式$’…’を使用すると、バックスラッシュでエスケープされた文字は、ANSIC標準で指定されているように置き換えられます。

4.3. セパレーターの選択

column -s $’\t’-tコマンドがどのように出力を調整するかを見てきました。 ただし、質問が出てくる場合があります。入力ファイルの内容にすでにタブが含まれている場合はどうなりますか?

はい、ファイルにタブが含まれている場合、このcolumnコマンドは期待どおりの結果を生成しません。

解決策は、pastおよびcolumnコマンドの区切り文字として未使用の文字を選択することです。

貼り付けコマンドの-dオプションを使用して、デフォルトのタブを上書きする区切り文字を設定できます。 後で、結果をパイプで送信するとコマンドでは、区切り文字と同じ文字を使用できます。

実際、未使用の文字を選択するのは簡単な作業ではありません。入力ファイルにどの文字が含まれているかを予測できないためです。

ただし、次のように、通常はファイルの内容に含まれない非表示の文字を選択できます。

  • \ a –アラートベル
  • \ e –エスケープ
  • \ f –フォームフィード
  • \ v –垂直タブ
  • \ x01 –見出しの開始
  • \ x02 –テキストの先頭

次に、区切り文字として「 \ a 」文字を使用して、きれいに配置された出力を取得する例を見てみましょう。

$ paste -d $'\a' left2.txt right2.txt | column -s $'\a' -t
I'm line1.                             Line1 on the right
I am line2, I am very very very long.  Line2 on the right
                                       Line3 on the right
Hi, I'm line4.                         Line4 on the right
                                       Line5 on the right
                                       Line6 on the right

上記の出力が示すように、期待どおりの結果が得られました。

ただし、2つのファイルの内容の間のセパレータをカスタマイズしたい場合があります。

また、出力を見ると、left2.txtが4行なのか6行なのかわかりません。 したがって、2つのファイルに含まれる行数が異なる場合、結果として、短いファイルにそれ以上の行がないことを識別したい場合があります。

次に、これらの要件を達成する方法を見てみましょう。

5. セパレータのカスタマイズと存在しないラインの識別

awk は強力なテキスト処理ユーティリティであり、確かに複数のファイルを処理できます。 awkはCのようなスクリプト言語を定義しました。 これにより、awkはテキストを処理するのに非常に柔軟になります。

文字列「 <-=-> 」を使用して、2つの入力ファイルから行を区切ります。

また、1つのファイルにこれ以上行がない場合は、次のテキストを表示します。 [ファイルが終了しました。 これ以上の行はありません] 「。

5.1. awkソリューション

それでは、awkがどのように問題を解決するかを見てみましょう。

awk -v sep='<- = ->' -v no_line_txt='[ File Ended. No More Lines ]' '
    NR==FNR { max_length = (length > max_length)? length : max_length
              left_lines = FNR
              left[FNR] = $0
              next
            }
    { printf "%-*s %s %s\n", max_length, (FNR in left? left[FNR] : no_line_txt), sep, $0 }
    END     { if (FNR < left_lines) {
                for (i=FNR+1; i <= left_lines; i++)
                    printf "%-*s %s %s\n", max_length, left[FNR], sep, no_line_txt
              }
            }
'  left2.txt right2.txt

上記のコマンドがどのような出力を生成するかを見てみましょう。

I'm line1.                            <- = -> Line1 on the right
I am line2, I am very very very long. <- = -> Line2 on the right
                                      <- = -> Line3 on the right
Hi, I'm line4.                        <- = -> Line4 on the right
[ File Ended. No More Lines ]         <- = -> Line5 on the right
[ File Ended. No More Lines ]         <- = -> Line6 on the right

良い! 出力はまさに私たちが期待したものです。

次に、awkコマンドがどのように機能するかを理解しましょう。

5.2. awk コマンドはどのように機能しますか?

それでは、 awk コードをウォークスルーして、それがどのように機能するかを理解しましょう。

awk -v sep='<- = ->' -v no_line_txt='[ File Ended. No More Lines ]' '

ここでは、2つのawk変数sepno_line_txtを宣言して、カスタムセパレーターと存在しない行のヒントを格納します。

次に、最初のファイルを処理します。

NR==FNR { max_length = (length > max_length)? length : max_length
          left_lines = FNR
          left[FNR] = $0
          next
        }

まず、最初のファイルを調べて、最長の行の長さ( max_length )と行数( left_lines )を調べます。 また、すべての行を連想配列に格納します: left[lineNumber]=行のテキスト

次に、awkは2番目の入力ファイルの読み取りを開始します。

{ printf "%-*s %s %s\n", max_length, (FNR in left? left[FNR] : no_line_txt), sep, $0 }

max_length、がわかっているので、 printf アクションを使用してパディングを動的に制御し、2番目のファイルの行を揃えることができます。

2番目のファイルのFNRが配列left[] にない場合、2番目のファイルの行数が最初のファイルより多いことを意味します。 これらの追加行については、左側にno_line_txtの値を出力します。

もちろん、左側と右側はsep変数で区切られています。

END     { if (FNR < left_lines) {
            for (i=FNR+1; i <= left_lines; i++)
                printf "%-*s %s %s\n", max_length, left[FNR], sep, no_line_txt
          }
        }

この例では、2番目のファイルには最初の入力ファイルよりも多くの行が含まれています。 ただし、入力ファイルが異なれば、状況は逆になる可能性があります。

したがって、 END ブロックでは、2番目のファイルの行数が少ないかどうかを確認する必要があります。 この場合、欠落している行の右側にno_line_txtを出力する必要があります。

最後に、awkコマンドに2つの入力ファイルをフィードする必要があります。

'  left2.txt right2.txt

最初の入力の行は左側に表示され、2番目のファイルの内容は右側に表示されます。

6. 結論

この記事では、例を通じて2つの入力ファイルを列ごとに組み合わせる方法を学びました。

ほとんどの場合、pastおよびcolumnコマンドは、目標を達成するのに役立ちます。

また、柔軟な awk スクリプトを使用すると、さらにカスタマイズを行うことができます。