$Id: shell2.txt,v 1.3 2002-01-27 21:48:25+09 tomokuni Exp tomokuni $ シェルを使おう - 日々のシェル - lilo-bk 友國哲男 (tomokuni@netfort.gr.jp) # この文書について # この文書の著作権は友國哲男 (tomokuni@netfort.gr.jp) が有します。 # この文書は GNU フリー文書利用許諾契約書 (GFDL) の第 1.1版 またはそれ以降の # 任意の版が定める条件の下で複製、配布、変更することができます。 # # Copyright (c) 2001 Tetsuo Tomokuni All rights reserved. # Permission is granted to copy, distribute and/or modify this document # under the terms of the GNU Free Documentation License, Version 1.1 # or any later version published by the Free Software Foundation; # with the Invariant Sections being LIST THEIR TITLES, with the # Front-Cover Texts being LIST, and with the Back-Cover Texts being LIST. # A copy of the license is included in the section entitled "GNU # Free Documentation License". # # GFDL は LICENSE ファイル、若しくは http://www.gnu.org/copyleft/fdl.html # を参照してください。 所要時間: 60 分(質疑応答込み)を予定 abstract: 6 月の LMS では尻切れとんぼになったので、こんどこそ、 ということで、今回は Shell のエッセンスや魅力が体感 できるような、それで且つ身近なものをテーマにします。 いろいろなシチュエーションで「こんなことできたらいいのに」 ということがよくありますが、そういうときに perl のような 高級インタプリタ等を使わずとも、 Shell の機能を使えば 結構できたりするものです。 そこで、そういうときに役立つような Shell プログラミング とコマンドライン上でのワンライナを例を用いて紹介し、 その応用を考えていきます。 以下では Bourne Shell 系のシェルを対象に解説していきます。 (一応の想定は Linux で /bin/sh として使われることが多い bash を前提としています。ですが、他の /bin/sh でも使える ように bash 特有な機能を積極的には使っていません。) これによって、少しでも UNIX の面白さが伝われば幸いです。 Content 1 ループを使おう 1.1 ファイル消去 1.2 ファイル名変換(大文字入り→小文字) 2 one line で書こう 2.1 改行コード変換 2.2 拡張子変換 2.3 ファイル消去 2.4 ファイルのバックアップ 3 ループとワンライナ 3.1 while と read を使った例 3.2 for を使った例 Acknowledgement 1 ループを使おう 1.1 ファイル消去 $ for file in *~ ; do rm -i $file; done ... ダメ。 $ for file in *~ ; do rm -i "$file"; done ... これでオーケー。 カレントディレクトリ以下のファイルを消去は、、、 2 章で 1.2 ファイル名変換(大文字入り→小文字) (1) すべてのファイルを mv する $ for file in * ; do mv $file `echo $file | tr 'A-Z' 'a-z'`; done (2) 名前に大文字が含まれているファイルのみ mv を実行 $ for file in * ; \ > do if test x`echo $file | tr -cs 'A-Z' '_' | tr -d '_'` != x ; \ > then mv $file `echo $file | tr 'A-Z' 'a-z'`; fi; done (3-1) 名前に大文字が含まれていて、かつ変更後の名前と同じ名前をもつ ファイルが既に存在していない場合のみ mv を実行 $ for file in * ; \ > do if test x`echo $file | tr -cs 'A-Z' '_' | tr -d '_'` != x \ > -a ! -f `echo $file | tr 'A-Z' 'a-z'`; \ > then mv $file `echo $file | tr 'A-Z' 'a-z'`; fi; done (3-2) GNU 版 test なら -e (exist) が使えるので $ for file in * ; \ > do if test x`echo $file | tr -cs 'A-Z' '_' | tr -d '_'` != x \ > -a ! -e `echo $file | tr 'A-Z' 'a-z'`; \ > then mv $file `echo $file | tr 'A-Z' 'a-z'`; fi; done (3-3) もっと簡単に $ for file in *; do dest=`echo $file | tr 'A-Z' 'a-z'`; \ > if test "$dest" != "$file" -a ! -f "$dest"; \ > then mv "$file" "$dest"; fi; done (参考) UNIX FAQ の 2.6 節にも同じような話題がある。 2 one line で書こう 2.1 改行コード変換 (1-1) UNIX -> Mac $ cat file.unix | tr '\n' '\r' > file.mac (1-2) Mac -> UNIX $ cat file.mac | tr '\r' '\n' > file.unix (2-1) DOS -> UNIX $ cat file.dos | tr -d '\r' > file.unix (2-2) DOS -> Mac $ cat file.dos | tr -d '\n' > file.mac (3-1) UNIX -> DOS $ cat file.unix | awk 'BEGIN{RS="\n";ORS="\r\n"}{print $0}' > file.dos $ cat file.unix | perl -ne 's/\n/\r\n/g; print;' > file.dos (3-2) Mac -> DOS $ cat file.mac | awk 'BEGIN{RS="\r";ORS="\r\n"}{print $0}' > file.dos $ cat file.unix | perl -ne 's/\r/\r\n/g; print;' > file.dos (注) RS: (Input) Record Separator ORS: Output Record Separator 他にも工夫すればいろいろあるので、いろいろ試してみるとよいだろう。 2.2 拡張子変換 "*.htm" を "*.html" に変換する。 $ for file in *.htm; do mv $file `basename $file htm`html; done bash/zsh なら $ for file in *.htm; do mv $file ${file%%.htm}.html; done 2.3 ファイル消去 カレントディレクトリ以下の "*~" (Emacs のバックアップファイル等) を消去する。 $ find . -name '*~' -exec rm {} \; $ find . -name '*~' -exec rm -i {} \; find の出力を xargs を使って処理することでも実現できる。 $ find . -name '*~' | xargs rm -i ... ダメ。 $ find . -name '*~' | xargs -p -r rm ... 空白文字入りファイルはダメ。 $ find . -name '*~' | xargs -0 -p -r rm ... これでもちょっと。。。 $ find . -name '*~' -print0 | xargs -p -r rm ... やっぱりダメ。 $ find . -name '*~' -print0 | xargs -0 -p -r rm ... やっと正解。 実は GNU 版 find を使えば簡単である。 $ find . -name '*~' -ok rm -i {} \; (注意) ディレクトリの階層が深いと実行時間が長くなるので、 そういう場合は find のオプションの -maxdepth 等を 使って辿る階層のレベルを制限して使用することもある。 2.4 ファイルのバックアップ 昨日増えた分を表示 $ find Mail -daystart -ctime 1 -print これをバックアップ $ find Mail -daystart -ctime 1 -print | \ > tar -T - -czf Backup/Mail`date '+%Y%m%d'`.tar.gz しかしこれではディレクトリが入ってしまうので、 $ find Mail -daystart -type f -ctime 1 -print | \ > tar -T - -czf Backup/Mail`date '+%Y%m%d'`.tar.gz 空白文字入りファイル対策 $ find Mail -daystart -type f -ctime 1 -print0 | \ > tar --null -T - -czf Backup/Mail`date '+%Y%m%d'`.tar.gz ログファイルを除く処理も入れる $ find Mail -path 'Mail/procmail.log' -prune \ > -o -daystart -type f -ctime 1 -print0 | \ > tar --null -T - -czf Backup/Mail`date '+%Y%m%d'`.tar.gz 更に一週間分は、、、 $ find Mail -daystart -ctime +0 -ctime -8 ... 3 ループとワンライナ 3.1 while と read を使った例 ファイルを *.bak という名前のファイルに移動(バックアップ)する。 リダイレクトを用いて ... $ while read file; do mv ${file} ${file}.bak; done < filelist パイプを使用すると ... $ cat filelist | while read file; do mv ${file} ${file}.bak; done 3.2 for を使った例 "*.c" (C ソース) のオリジナルとカレントを各ファイル毎に比較し ファイルが変更されているかどうかを表示する。 $ for file in *.c; \ > do (diff ../orig/${file} ${file} >/dev/null \ > && echo ${file} is not changed.) \ > || echo ${file} is changed. ; done | tee diff.log Acknowledgement 参考にしたもの Bourne Shell 自習テキスト (http://www.tsden.org/takamiti/shText/) プロフェッショナルシェルプログラミング (アスキー, ISBN4-7561-1632-9) 井上さんのページ (http://www.ainet.or.jp/~inoue/memo/shell.html) 確認に使わせていただいたシェル しらいさん作の FD shell (hp.vector.co.jp/authors/VA012337/soft/fd/fd2.html)