ファイルがいくつか置いてあるフォルダ(を用意して)で、以下のコマンド列を 実験してみて下さい。
成功すれば xxxx.bak というファイルが多数つくられるが、このコマンドで クリアできる(のは、わかりますね?)
rm *.bak # *の右側にスペースを入れないこと
後述のいずれかの方法でバックアップととるときに、cp コマンドでなく mv コマンドを使ってしまった場合、
以下のコマンドで復旧できます
exprコマンド に3つの引数を渡し、
2つめがコロン :
ならば、
3つめを正規表現だと解釈し、1つめが3つめに「マッチ」するか
どうかを調べ(終了ステータスを返す)、
標準出力には、正規表現(またはその中でキャプチャ記号
\(
.. \)
で囲まれた部分)にマッチした部分を出力する。
なお、板書(右図)ではファイル名を ダブルクォート ”” で囲むことはしていない
(様々な場面に対応する必要がないならそれでOK)
for f in *.bak ; do mv "$f" `expr "$f" : '\(.*\).bak' ` ; done
この課題に対してはアプローチ1のほうが簡単に書けるので先に提示するが、
様々な考え方を知るために両方のアプローチを見ておくといいだろう。
for f in * ; do
test -f "$f" && cp "$f" "$f.bak"
done
このアプローチではbash(をはじめとするどのシェルにもある)の for構文による繰り返しを用いる。
*
は
パス名展開によって、ファイル名デイィレクトリ名のリストに展開され、
変数 f (ループ変数)に1つ1つが代入され、
ループ本体(2行目)が繰り返される。
変数は $f
として引用されている。
“$f”
のようにダブルクォート “ “ で囲むのは、
(たとえばWindowsの Program Files のように) スペース文字を含む名前の時に誤作動しないように予防策。
ちなみに、シングルクォート ‘ ‘ で囲んだ場合は変数展開が行われない
test コマンドで、ファイルかどうかテストできる。
&&で接続されており、 左側のコマンド(テスト)に成功したとき(に限り)右側のコマンドが 実行される。
一行で書いたバージョンも以下に載せておく。
for f in * ; do test -f "$f" &&echo cp "$f" "$f.bak" ; done
(補足)testコマンド おさらい
右図のように検査を行った結果によって0または非0の 「終了ステータス」を残すことがコマンドの主目的。
図のように、test という名前で呼び出すことも、 [
(左ブラケット)という(コマンド名としては変だが)名前で呼び出すこともでき、
後者(左ブラケット)の時は最後に右ブラケット ]を置くことが求められる。
(このことでプログラムの一部のような見かけが実現できる)
以下の2つの書き方は同じ意味になる。
こういうtest(が残した終了ステータス)で動作を
コントロールするための制御構造として、if,
(前述の)&&
以外に、
繰り返しを行うための while, until 等もある。
ls コマンドは(パラメータとしてファイル名などを指定せずに呼んだ場合)、 カレントディレクトリにある、ファイル名とやディレクトリ名を区別せず 名前だけを列挙する。
ls コマンドの出力から、その名前がファイルなのかディレクトリなのかを 判別する方法は大別して2つある。
ls -l
詳細出力。
行の最初の文字が ‘d’ ならディレクトリ。
’-‘ ならファイル(他にも数種類ある)
ls -F
ファイル名の後ろに、ディレクトリなら ‘/’ シンボリックリンクなら ‘@’ がつく。
通常のファイルには何もつかないが、実行可能なものには ’*’ がつく。
この出力から、ファイル(実行可能なものも含めて)の名前を抜き出すには、
ls -F | grep -v '[/@]$' | sed 's/*$//'
sed コマンドは s サブコマンドを使って、置換(substitute)を行う。
s/ ... / ... /
の 左側の … の部分に書かれたパターン
(にマッチする部分)を、
右側の … に書かれた文字並びに置き換える。
*
を取り除く)。前節のコマンド列を使う方法として、以下の方法があり得るだろう。
コマンド置換: 前節の出力を、コマンド置換で for のリストの場所に展開させる。
for f in `ls -F|grep -v '[/@]$'|sed 's/*$//'` ; do
cp "$f" "$f.bak"
done
while read で 1つずつ処理:
read もコマンドで、
前節の出力を while構文にパイプでつないで使う。
ls -F | grep -v '[/@]$' | sed 's/*$//' |
while read f ; do
cp "$f" "$f.bak"
done
sed でコマンドの流れを作り、シェルに渡す。
ls -F | grep -v '[/@]$' |
sed -e 's/*$//' -e 's/\(.*\)/cp "\1" "\1.bak"/' | sh
sed はサブコマンドを複数与えることができる。
2つめのサブコマンドでは、左側のパターンの中に ( … ) という並びがあり、
その中にあるパターンにマッチした文字列 (ここでは .* なので、行全体にマッチする)を「キャプチャ」し、
右側で \1 \2 \3 という記号で「後方参照」して引用できる。
2つめのサブコマンドは、スペースを中に含む(さらに ““も含む)ため、 ‘ ‘ でで囲んである。
sh(dashであることもある) は、 bashから会話を支援する進んだ機能を取り除いた、 純粋なコマンドインタプリタ(Unix草創期からある B-シェルと同等)で、
パイプの後段に置かれると、標準入力から与えられたものを コマンド(スクリプト)として実行する。
以上の3つをそれぞれ一行バージョンにしたものも置いておく。
for f in `ls -F|grep -v '[/@]$'|sed 's/*$//'` ; do cp "$f" "$f.bak" ;done
ls -F|grep -v '[/@]$'|sed 's/*$//' | while read f ; do cp "$f" "$f.bak" ; done
ls -F|grep -v '[/@]$'|sed -e 's/*$//' -e 's/\(.*\)/cp "\1" "\1.bak"/' | sh
test -f "$f" && ( expr "$f" : '.*[.]bak' || cp "$f" "$f.bak" )
expr "$f" : '.*[.]bak' || test -f "$f" && cp "$f" "$f.bak"
ls -F | grep -v '[/@]$' | sed 's/*$//'
ls -F | grep -v '[/@]$' | grep -v '.bak$' | sed 's/*$//'