概要
教える:30分
演習する。 15分問題
コマンドを保存して再利用するには?
目的
ファイルの固定セットに対してコマンドまたはコマンドのシリーズを実行するシェルスクリプトを作成します。
ユーザーがコマンドラインで定義した一連のファイルに対して操作するシェルスクリプトを書く。
自分や他の人が書いたシェルスクリプトを含むパイプラインを作成する。歴史的な理由から、ファイルに保存されたコマンドの束は通常シェルスクリプトと呼ばれますが、これらは実際には小さなプログラムであることに間違いはありません。
molecules/
に戻って、シェルスクリプトとなるmiddle.sh
という新しいファイルを作成するところから始めましょう:$ cd molecules$ nano middle.sh
コマンド
nano middle.sh
はテキストエディター ‘nano’ (shell で実行) 内でファイルmiddle.sh
をオープンします。ファイルが存在しない場合は作成されます。テキストエディタを使ってファイルを直接編集するには、次の行を挿入するだけです:head -n 15 octane.pdb | tail -n 5
これは以前に構築したパイプのバリエーションです: ファイル
octane.pdb
の 11-15 行を選択するものです。そして、ファイルを保存し (nano の
Ctrl-O
) 、テキストエディタを終了します (nano のCtrl-X
). ディレクトリmolecules
にmiddle.sh
というファイルがあることを確認します。ファイルを保存したら、そのファイルに含まれるコマンドを実行するようにシェルに要求できます。私たちのシェルは
bash
という名前なので、次のコマンドを実行します:$ bash middle.sh
ATOM 9 H 1 -4.502 0.681 0.785 1.00 0.00ATOM 10 H 1 -5.254 -0.243 -0.537 1.00 0.00ATOM 11 H 1 -4.357 1.252 -0.895 1.00 0.00ATOM 12 H 1 -3.009 -0.741 -1.467 1.00 0.00ATOM 13 H 1 -3.172 -1.337 0.206 1.00 0.00
確かに、このパイプラインを直接実行したら得られるものとまったく同じスクリプト出力になります。
Text vs. Whatever
通常、Microsoft Word や LibreOffice Writer などのプログラムを「テキストエディタ」と呼びますが、プログラミングに関してはもう少し注意する必要があります。 デフォルトでは、Microsoft Word は
.docx
ファイルを使用して、テキストだけでなく、フォントや見出しなどの書式情報も保存します。 この余分な情報は文字として保存されないので、head
のようなツールには意味がありません。彼らは、入力ファイルが標準的なコンピュータキーボードの文字、数字、句読点以外何も含んでいないことを期待しています。 したがって、プログラムを編集するときは、プレーンテキストエディタを使うか、ファイルをプレーンテキストで保存するように注意しなければならない。任意のファイルから行を選択したい場合はどうするか。ファイル名を変更するたびに
middle.sh
を編集してもいいが、シェルでもう一度コマンドを入力して、新しいファイル名で実行するより時間がかかるかもしれない。$ nano middle.sh
次に “nano” 内で
octane.pdb
というテキストをという特殊変数に置き換えます:
head -n 15 "" | tail -n 5
シェルの中では
とは「コマンドライン上の最初のファイル名(あるいは他の引数)」 という意味です。これで、
$ bash middle.sh octane.pdb
ATOM 9 H 1 -4.502 0.681 0.785 1.00 0.00ATOM 10 H 1 -5.254 -0.243 -0.537 1.00 0.00ATOM 11 H 1 -4.357 1.252 -0.895 1.00 0.00ATOM 12 H 1 -3.009 -0.741 -1.467 1.00 0.00ATOM 13 H 1 -3.172 -1.337 0.206 1.00 0.00
または別のファイルでこのようにスクリプトを実行できるようになった。
$ bash middle.sh pentane.pdb
ATOM 9 H 1 1.324 0.350 -1.332 1.00 0.00ATOM 10 H 1 1.271 1.378 0.122 1.00 0.00ATOM 11 H 1 -0.074 -0.384 1.288 1.00 0.00ATOM 12 H 1 -0.048 -1.362 -0.205 1.00 0.00ATOM 13 H 1 -1.183 0.500 -1.412 1.00 0.00
引数のダブルクォート
ループ変数をダブルクォートの中に置いたのと同じ理由で、ファイル名がスペースを含んでいた場合、
をダブルクォートで囲みます。
現在、返される行の範囲を調整したい場合は、毎回
middle.sh
を編集する必要があります。 この問題を解決するために、3つのコマンドライン引数を使用するようにスクリプトを構成してみましょう。 最初のコマンドライン引数 () の後、私たちが提供する追加の各引数は、特殊変数
,
,
を介してアクセス可能で、それぞれ第1、第2、第3のコマンドライン引数を参照することができます。
これを知っていれば、追加の引数を使用して、それぞれ
head
とtail
に渡す行の範囲を定義できます:$ nano middle.sh
head -n "" "" | tail -n ""
これで実行できるようになりました。
$ bash middle.sh pentane.pdb 15 5
ATOM 9 H 1 1.324 0.350 -1.332 1.00 0.00ATOM 10 H 1 1.271 1.378 0.122 1.00 0.00ATOM 11 H 1 -0.074 -0.384 1.288 1.00 0.00ATOM 12 H 1 -0.048 -1.362 -0.205 1.00 0.00ATOM 13 H 1 -1.183 0.500 -1.412 1.00 0.00
コマンドへの引数を変更することにより、スクリプトの動作を変更することが可能です。
$ bash middle.sh pentane.pdb 20 5
ATOM 14 H 1 -1.259 1.420 0.112 1.00 0.00ATOM 15 H 1 -2.608 -0.407 1.130 1.00 0.00ATOM 16 H 1 -2.540 -1.303 -0.404 1.00 0.00ATOM 17 H 1 -3.393 0.254 -0.321 1.00 0.00TER 18 1
これは動作しますが、次に
middle.sh
を読む人はこれが何をするか理解するのに時間がかかるかもしれません。$ nano middle.sh
コメントは
#
文字で始まり、行末まで続きます。コンピュータはコメントを無視しますが、(未来の自分を含め)人々がスクリプトを理解し使用できるようにするには非常に貴重です。読者を間違った方向に導くような説明は、全くないよりも悪い。1つのパイプラインで多くのファイルを処理したい場合はどうすればよいのでしょうか。たとえば、
.pdb
ファイルを長さでソートしたい場合、次のように入力します:$ wc -l *.pdb | sort -n
なぜなら、
wc -l
はファイルの行数をリストし (wc
は ‘word count’ を意味し、-l
オプションは代わりに ‘count lines’ という意味です)、sort -n
は数値的にものを並べ替えるからです。これをファイルに書くこともできるが、そうするとカレントディレクトリにある.pdb
個のファイルしかソートされない。もし、他の種類のファイルもソートされたリストを得たいのであれば、それらの名前をスクリプトに取り込む方法が必要である。また、スペースを含む引数("$@"
は特殊な構文で、""
""
…と同等)を処理するために$@
をダブルクオートで囲む必要があります。以下はその例である。
$ nano sorted.sh
# Sort files by their length.# Usage: bash sorted.sh one_or_more_filenameswc -l "$@" | sort -n
$ bash sorted.sh *.pdb ../creatures/*.dat
9 methane.pdb12 ethane.pdb15 propane.pdb20 cubane.pdb21 pentane.pdb30 octane.pdb163 ../creatures/basilisk.dat163 ../creatures/minotaur.dat163 ../creatures/unicorn.dat596 total
固有種一覧
Leah には数百個のデータファイルがあって、それぞれがこのようにフォーマットされる。
2013-11-05,deer,52013-11-05,rabbit,222013-11-05,raccoon,72013-11-06,rabbit,192013-11-06,deer,22013-11-06,fox,12013-11-07,rabbit,182013-11-07,bear,1
このタイプのファイルの例は
data-shell/data/animal-counts/animals.txt
に示されています。
animals.txt
の固有種を出すには、cut -d , -f 2 animals.txt | sort | uniq
というコマンドを使います。コマンドライン引数として任意の数のファイル名を取り、上記のコマンドのバリエーションを使用して、これらのファイルのそれぞれに現れる固有種のリストを個別に表示する
species.sh
というシェルスクリプトを作成します。Solution
# Script to find unique species in csv files where species is the second data field# This script accepts any number of file names as command line arguments# Loop over all filesfor file in $@doecho "Unique species in $file:"# Extract species namescut -d , -f 2 $file | sort | uniqdone
何か役に立つ一連のコマンドを実行したとします – たとえば、論文で使用するグラフを作成しました。 後で必要であればグラフを再作成できるので、コマンドをファイルに保存したいと思います。もう一度入力する代わりに (そして、間違って入力する可能性もあります)、次のようにします:
$ history | tail -n 5 > redo-figure-3.sh
ファイル
redo-figure-3.sh
には現在次のものが含まれています。297 bash goostats NENE01729B.txt stats-NENE01729B.txt298 bash goodiff stats-NENE01729B.txt /data/validated/01729.txt > 01729-differences.txt299 cut -d ',' -f 2-3 01729-differences.txt > 01729-time-series.txt300 ygraph --format scatter --color bw --borders none 01729-time-series.txt figure-3.png301 history | tail -n 5 > redo-figure-3.sh
エディタでコマンドのシリアル番号を削除し、
history
コマンドを呼び出した最終行を削除すると、その図をどのように作成したかを完全に記録することができました。なぜコマンドを実行する前に履歴に記録するのか?
コマンドを実行した場合:
$ history | tail -n 5 > recent.sh
ファイル内の最後のコマンドは
history
コマンドそのもの、すなわち。つまり、シェルは実際に実行する前にhistory
をコマンドログに追加しているのである。 実際、シェルは常にコマンドを実行する前にログに追加しています。 あるコマンドが原因で何かがクラッシュまたはハングアップした場合、その問題を調査するために、そのコマンドが何であったかを知ることは有用である。実際には、ほとんどの人がシェル プロンプトでコマンドを数回実行して正しいことを確認し、それをファイルに保存して再利用することによってシェル スクリプトを開発します。 スクリプトの作成
Nelle の上司は、彼女のすべての分析が再現可能でなければならないと主張しました。 すべてのステップをキャプチャする最も簡単な方法は、スクリプトにすることです。
最初に、Nelle のデータ ディレクトリに戻ります。
$ cd ../north-pacific-gyre/2012-07-03/
彼女はエディタを実行し、次のように書き込みます。
$ bash do-stats.sh NENE*.txt
このようにすることもできます:
$ bash do-stats.sh NENE*.txt | wc -l
処理したファイル名ではなく、単に処理したファイルの数だけ出力するようにするため。
Nelle のスクリプトについて注意すべき点は、実行者がどのファイルを処理するかを決定できることです。欠点は、常にこれらのファイルだけを選択することで、スクリプトを編集しないと、すべてのファイル (Z ファイルを含む) や南極の同僚が作成する ‘G’ または ‘H’ ファイルに対して実行することができません。もし、もっと冒険をしたければ、コマンドライン引数をチェックするようにスクリプトを修正し、何も提供されなければ
NENE*.txt
を使用することができる。もちろん、これは柔軟性と複雑さの間の別のトレードオフをもたらす。シェル スクリプトの変数
molecules
ディレクトリに、次のコマンドを含むscript.sh
というシェル スクリプトがあると想像してください。head -n tail -n
molecules
ディレクトリにいるときに、次のコマンドを入力します:bash script.sh '*.pdb' 1 1
次のどの出力を期待しますか?
molecules
ディレクトリ内の.pdb
で終わる各ファイルの先頭行と最終行の間のすべての行molecules
ディレクトリ内の.pdb
で終わる各ファイルの先頭行と最終行の間のすべての行 directory- The first and last line of each file in
molecules
directory- An error because quotes around
*.pdb
Solution
正解は2である。
特殊変数$1、$2、$3はthescriptに与えられたコマンドライン引数を表し、実行されるコマンドは次のようになります:
$ head -n 1 cubane.pdb ethane.pdb octane.pdb pentane.pdb propane.pdb$ tail -n 1 cubane.pdb ethane.pdb octane.pdb pentane.pdb propane.pdb
シェルは
'*.pdb'
を引用符で囲っているので展開しない。その結果、スクリプトへの最初の引数は'*.pdb'
で、これはhead
とtail
によってthescript内に展開されます。Find the Longest File With a Given Extension
ディレクトリ名とファイル名拡張子を引数に取り、そのディレクトリ内でその拡張子を持つ行数の最も多いファイル名を出力する
longest.sh
というシェルスクリプトを書いてみてください。 例えば、$ bash longest.sh /tmp/data pdb
は、
/tmp/data
にある.pdb
ファイルのうち、最も行数の多いものの名前を表示する。Solution
# Shell script which takes two arguments:# 1. a directory name# 2. a file extension# and prints the name of the file in that directory# with the most lines which matches the file extension.wc -l /*. | sort -n | tail -n 2 | head -n 1
パイプラインの最初の部分
wc -l /*. | sort -n
は、各ファイルの行数を数え、数値順にソートします(最大行が最後です)。 複数のファイルがある場合、wc
は最後のサマリー行も出力し、全ファイルの行数の合計を示す。
wc -l /*. | sort -n | tail -n 1
で最後のサマリー行を見ます。出力を確実に理解するために、パイプラインをバラバラに構築することができます。このディレクトリには、あなたが作成した他のファイルに加えて、いくつかの.pdb
ファイルが含まれています。次の3つのスクリプトをそれぞれbash script1.sh *.pdb
、bash script2.sh *.pdb
、bash script3.sh *.pdb
として実行すると、どのようになるか説明しなさい。# Script 1echo *.*
# Script 2for filename in do cat $filenamedone
# Script 3echo [email protected]
Solutions
それぞれの場合、シェルは
*.pdb
でワイルドカードを展開し、結果のファイル名のリストをスクリプトへの引数として渡します。スクリプト 1 は、名前にドットを含むすべてのファイルのリストを表示します。スクリプトに渡された引数は、実際にはスクリプトのどこでも使用されません。
スクリプト 2 は、
.pdb
ファイル拡張子を持つ最初の 3 ファイルの内容を表示します。,
,
はそれぞれ第1、第2、第3引数を意味する。
Script 3 はスクリプトへのすべての引数 (すなわちすべての
.pdb
ファイル) と.pdb
が続くものを表示する。cubane.pdb ethane.pdb methane.pdb octane.pdb pentane.pdb propane.pdb.pdb
Debugging Scripts
Nelle の
north-pacific-gyre/2012-07-03
ディレクトリのdo-errors.sh
というファイルに次のスクリプトを保存したとする。# Calculate stats for data files.for datafile in "$@"do echo $datfile bash goostats $datafile stats-$datafiledone
これを実行すると、出力が空白になります。原因を調べるには、
-x
オプションを使用してスクリプトを再実行します。bash -x do-errors.sh NENE*.txt
出力は何を示していますか。どの行がエラーの原因になっていますか。
Solution
-x
オプションは、bash
をデバッグモードで実行させます。これは、実行された各コマンドを印刷し、エラーを見つけるのに役立ちます。 ループ変数名にタイプミスがあり、変数datfile
が存在しないため、空の文字列を返しています。Key Points
コマンドをファイル(通常シェルスクリプトと呼ばれる)に保存して再利用することができます。
bash
はファイルに保存したコマンドを実行します。
$@
はシェルスクリプトのすべてのコマンドライン引数を参照しています。 は、最初のコマンドライン引数、2番目のコマンドライン引数などを指します。値にスペースが含まれる可能性がある場合、変数を引用符で囲みます。
処理するファイルをユーザーに決めさせることは、より柔軟で内蔵 Unix コマンドと一致します。