Edited at

bashで複数のコマンドを一つのコマンドとして扱う方法(複合コマンド)

More than 3 years have passed since last update.

bashで複数のコマンドの結果(標準出力、標準エラー出力)をまとめて取得したいことが多々あると思う。

思いつく限りの方法をまとめてみた。


課題

せっかくなので、実現したい課題として以下のものを作るということで、内容を進めていく。


  • テキストファイルの先頭1行、末尾1行をパイプラインへ渡し、grepをかける。


別シェルを作成して実行

まず最初に愚直にシェルスクリプトを作成してしまうことが考えられるだろう。


head_and_tail.sh

input=${1}

head -n 1 ${input}
tail -n 1 ${input}

$ bash head_and_tail.sh input.txt | grep "PATTERN"

簡単に実現できるが、この程度のことでわざわざシェルスクリプトを作成していては切りがないだろう。


関数を作成して実行

次に思いつくのが関数を作ってしまう方法だろう。

function head_and_tail() {

input=${1}
head -n 1 ${input}
tail -n 1 ${input}
}
head_and_tail input.txt | grep "PATTERN"

ファイルを作成するよりは、実行しているプロンプトで直接打つことができるので現実的だが、まだ冗長だし毎回書くのはめんどくさい。

# もしよく利用するならば、このままbashrcに書くのが一番だろう。


一度ファイルに書き出す。

head、tailそれぞれの結果を一度ファイルに格納してしまうのも一つの手だろうか。

head -n 1 input.txt > tmp

tail -n 1 input.txt >> tmp
cat tmp | grep "PATTERN"

簡単に実現できる手ではあるが、あまりやりたくはない。


複合コマンド

さぁ、今回の本命である複合コマンドを使ってみよう。

複合コマンドとひとくくりに言ってもたくさんあるが、今回利用するのは以下である。


  • (list) : サブシェル実行


(list)

listはサブシェル内で実行されます(後述のコマンド実行環境の項を参照)。シェルの環境に影響を与えるような変数の代入や組み込みコマンドは、コマンドの終了後に影響を残しません。返却ステータスは list の終了ステータスです。



  • { list; } : グループコマンド


{ list; }

list が単に現在のシェル環境で実行されます。listの最後は改行文字かセミコロンでなければなりません。これは グループコマンド(group command) と呼ばれます。返却ステータスはlistの終了ステータスです。メタ文字である ( や ) と違い、 { と } は予約語であり、予約語として認識される場所に現われる必要があることに注意してください。これらは単語分割の対象とならないため、 リスト との間が空白またはシェルのメタ文字で分かれている必要があります。


説明はいろいろ書いてあるが、結局のところは複数のコマンドを1つのコマンドとして使用することができるということである。

これを使って先の課題を実行してみよう。

( head -n 1 input.txt ; tail -n 1 input.txt ; ) | grep "PATTERN"

{ head -n 1 input.txt ; tail -n 1 input.txt ; } | grep "PATTERN"

こんな感じになる。

若干冗長ではあるが、1行でかけるのはやはり魅力的ではないだろうか。

ちなみに、複数行に分けて記述してもかまわない。

こちらの方が書きやすいかも知れない。

(

head -n 1 input.txt
tail -n 1 input.txt
) | grep "PATTERN"

{

head -n 1 input.txt
tail -n 1 input.txt
} | grep "PATTERN"


(list)と{ list; }の違い

さて、(list)と{ list; }の何が違うの?と思っただろう。

この2つの書き方の大きな違いは、シェル環境の違いである。

(list):サブシェル実行

 現在実行しているシェルとは別の環境によって実行される。

{ list; }:同一シェル環境での実行

 現在実行しているシェルと同一の環境で実行される。

具体的にどんな事かというと、変数への代入を取り上げるとわかりやすい。

var="value1"

echo $var # value1
(var="value2"; echo $var ) # value2 (サブシェル内の変数varを変更している)
echo $var # value1 # サブシェル内の変数とは別もののため、変更されずに残っている。
{ var="value3"; echo $var; } # value3 同一のシェル環境のため、変更が反映される。
echo $var # value3

また、サブシェルはちゃんと別のプロセスが立ち上がっている。

サブシェルのPIDを確認する方法は以下である。

#以下の例だと、(list)のものだけ別のPIDが返ってくる。

#PIDについて取り上げるのは、かなり面倒なので、ここでは深く触れない。

echo $BASHPID

( echo $BASHPID )
{ echo $BASHPID; }


その他

複合コマンドを使いたいがために、わざわざhead、tailコマンドを使っていたが、

awkやsedを使えばもっと簡単にできたりするのは当たり前。。。

sedが一番単純ですかねw


awkコマンド

awk '{if(NR==1){print $0}}END{print $0}' input.txt | grep "PATTERN"


sedコマンド

sed -n '1p;$p' input.txt | grep "PATTERN"