10のUNIX小技

2007/1/11

IBMのサイトで「Learn 10 good UNIX usage habits」という記事が発表されていました。 面白かったので要約してみました。 変な部分があるかも知れないので詳細は原文をご覧下さい。

原文とは一部異なります。 本ページスペースなどの関係でコマンド引数などを短く省略しています。 原文のサンプルコマンドが間違っていたりするので、修正している部分もあります。 原文を修正しているのは、tar.gzをzオプションを使わないでxfvしようとしているところと、xargsにlsではなくls -lを渡している部分です。 あと、説明文を短くしてしまっています。


1. ディレクトリの作成

良く使うコマンドの一つであるmkdirですが、面倒臭い使い方をしていませんか?

悪い例


~/ $ mkdir a
~/ $ cd a
~/a $ mkdir b
~/a $ cd b
~/a/b/ $ mkdir c
~/a/b/ $ cd c
~/a/b/c $

良い例


mkdir -p a/b/c

良い例2:複雑な構造をコマンド一発で作る例


mkdir -p a/{lib,src,doc/{html,pdf,txt}}

mkdirに-pオプションがない場合

mkdirhierを使うと同様の事ができるそうです。


mkdirhier -p a/{lib,src,doc/{html,pdf,txt}}

2. tarの解凍先を指定する

tarを解凍するために目的のディレクトリまでtarファイルを移動するのも悪い慣習の一つです。 tarの-Cオプションを使えば好きなディレクトリへtarファイルを解凍できます。


tar xfv a.tar -C /tmp/a/b

-Cオプションを使う方法は、tarファイルを移動して、cdでディレクトリを移動して、tarコマンドで解凍をするやり方よりも推奨されます。 特に、アーカイブファイルが自分ではない誰かのものである場合には。


3. コマンドを演算子で繋げる

「;」を使って複数のコマンドを1行で繋げられる事は知っていると思います。 これは複数のコマンドを実行するには便利ですが、万能ではありません。 例えば、「;」を使って2つのコマンドを実行するとします。 二つ目のコマンドが一つ目のコマンドに依存する場合、一つ目のコマンドが失敗した場合には、二つ目のコマンドは必ず失敗します。 そのような場合には「;」ではなく、より適切な演算子を使うと良いでしょう。

一つ目のコマンドが成功した(return 0)場合にだけ実行


cd tmp/a/b/c && tar xvf ~/archive.tar

一つ目のコマンドが失敗した(return 0以外)場合にだけ実行


cd tmp/a/b/c || mkdir -p tmp/a/b/c


cd tmp/a/b || mkdir -p tmp/a/b && tar xvf -C tmp/a/b ~/a.tar


4. 変数と「"」の組み合わせは慎重に

特別な理由が無い限り、変数は「"」で囲う方が良いでしょう。 もし、変数名の直後に文字を使いたいのであれば、変数名を「{}」で囲いましょう。 これを忘れてしまうと、別の変数名として扱われてしまいます。 意図しない別の変数名になってしまった場合、大抵はnull値になるでしょう。


$ ls tmp/
a b
$ VAR="tmp/*"
$ echo $VAR
tmp/a tmp/b
$ echo "$VAR"
tmp/*
$ echo $VARa

$ echo "$VARa"

$ echo "${VAR}a"
tmp/*a
$ echo ${VAR}a
tmp/a
$


5. 長い入力にはエスケープシーケンスを利用

シェルスクリプト内で長いコマンドを表すために「\」が使われているを見た事がある人は多いと思われます。 しかし、手でコマンドを打つときに「\」を使う人は意外に少ないのではないでしょうか。 「\」を使って単一の行を複数行に分けて入力する方式は、ターミナルが複数行へのwrappingに対応していないときに便利です。


$ cd tmp/a/b/c || \
> mkdir -p tmp/a/b/c && \
> tar xvf -C tmp/a/b/c ~/archive.tar


$ cd tmp/a/b/c \
>                 || \
> mkdir -p tmp/a/b/c \
>                    && \
> tar xvf -C tmp/a/b/c ~/archive.tar

(注意) 大抵のshellでは、上矢印などで履歴を利用すると複数行で入力したコマンドであっても1行として出てきてしまいます。


6. コマンドをグループ分けする

大抵のシェルには、コマンドをグループ分けして実行する機能を持っています。 一般的には、subshellで行う方法と、現在実行しているshellで行う2通りの方法があります。

subshell

subshellを使う方法は、環境変数を定義しなおしたい場合に有効です。 subshellで環境変数を変更しても、現在実行中のshellが保持する環境変数は維持されます。


$ ( cd tmp/a/b/c/ || mkdir -p tmp/a/b/c && \
> VAR=$PWD; cd ~; tar xvf -C $VAR archive.tar ) \
> | mailx admin -S "Archive contents"

現在のshell

「{}」の前後にスペースが入るように注意しましょう。 スペースを入れないと正しく動作しないかも知れません。

また、「}」の前に来る最後のコマンドの後に「;」を入れるのを忘れないようにしましょう。


$ { cp ${VAR}a . && chown -R guest.guest a && \
> tar cvf newarchive.tar a; } | mailx admin -S "New archive"


7. xargsをfind以外と組み合わせる

xargsはfindが出力するファイル名を使って他のコマンドを実行するために使われます。 例えば、以下のようにfindが発見したファイル名をxargsに渡して処理をさせます。


$ find  何か  どこかへのパス | \
> xargs ファイル名を使う何らかのコマンド

しかし、xargsは単なるfindの補助ツールではありません。

利用例


$ xargs
a
b
c
コントロールキー+p
a b c

ファイル名を出力する他のコマンドと組み合わせる事も可能です。


$ ls | xargs
December_Report.pdf README a archive.tar mkdirhier.sh
$ ls | xargs file
December_Report.pdf: PDF document, version 1.3
README: ASCII text
a: directory
archive.tar: POSIX tar archive
mkdirhier.sh: Bourne shell script text executable

複数行を一行にまとめる事も出来ます。


$ ls -l | xargs
-rw-r--r-- 7 joe joe 12043 Jan 27 20:36 December_Report.pdf -rw-r--r-- 1 \
root root 238 Dec 03 08:19 README drwxr-xr-x 38 joe joe 354082 Nov 02 \
16:07 a -rw-r--r-- 3 joe joe 5096 Dec 14 14:26 archive.tar -rwxr-xr-x 1 \
joe joe 3239 Sep 30 12:40 mkdirhier.sh

xargs利用上の注意

「_(アンダースコア、下棒)」はEOFとして扱われます。 そのため、その文字がxargsに渡されると、それ以降の項目は無視されてしまいます。 この問題を回避するには-eオプションを利用してください。


8. grepに行数をカウントさせる

wc -l と組み合わせるのではなく、grep の-cオプションを使った方が行数の計算は速いです。


$ time grep PATTERN longfile.txt | wc -l
2811

real    0m0.097s
user    0m0.006s
sys     0m0.032s
$ time grep -c PATTERN longfile.txt
2811

real    0m0.013s
user    0m0.006s
sys     0m0.005s

grepがカウントすべきではないとき

-cオプションは、マッチした行数のみをカウントします。 -oオプションは、マッチしたパターン自体を出力します。 単一行にマッチするパターンが複数回登場する時などには、-oオプションにより標準出力に出される行数と、-c が行った結果のカウント数が異なってしまいます。 そのような場合には、wc -l を使いましょう。


grep -o PATTERN longfile.txt | wc -l


9. パターンマッチは行全体ではなくフィールドで

grepなどで行全体をマッチするのではなく、awkで特定のフィールドを指定してパターンマッチをすべきです。

grepで行全体をマッチさせてしまうと、意図しないものも結果に入ってしまうことがあります。 例えば、ファイル更新日が12月のものを探している時に、1月に更新したDecemberReport.pdfが結果に入ってしまう場合があります。

悪い例


ls -l | grep Dec
-rw-r--r--  7 joe joe  12043 Jan 27 20:36 December_Report.pdf
-rw-r--r--  1 root root  238 Dec 03 08:19 README
-rw-r--r--  3 joe joe   5096 Dec 14 14:26 archive.tar

良い例


ls -l | awk '$6 == "Dec"'
-rw-r--r--  3 joe joe   5096 Dec 14 14:26 archive.tar
-rw-r--r--  1 root root  238 Dec 03 08:19 README

10. 無駄なcatとパイプは行わない

grepなどのコマンドを使う時に、catをパイプで繋げてgrepの標準入力に流し込むのは無駄です。 grepなどの大抵のコマンドには、ファイル名をオプションとして渡せる機能があります。 以下に速度の比較を示します。


$ time cat longfile.txt | grep PATTERN
2811

real    0m0.015s
user    0m0.003s
sys     0m0.013s

$ time grep PATTERN longfile.txt
2811

real    0m0.010s
user    0m0.006s
sys     0m0.004s


追記 : 2007/1/12

「2. tarの解凍先を指定する」のサンプルコマンドが間違っているというご指摘を多数の方から頂きました。 GNU tarで動くコマンドに修正しました。 間違いをご指摘くださった皆様ありがとうございました。

最近のエントリ

過去記事

過去記事一覧

IPv6基礎検定

YouTubeチャンネルやってます!