警告
本資料は参考情報として提供されています。内容についてのご質問には必ずしもお答えできない可能性がございます。
7. 一歩進んだシェルの使い方¶
ここでは、CLIでの作業をより便利にするシェルの機能について記述します。
7.1. コマンド置換¶
あるコマンドの結果を、別のコマンドの引数として渡したい場合があります。例えば、あるファイルのリストが保存されていたときに、それをls
コマンドに渡す例を考えます。
$ cat list.txt
1.txt
2.txt
3.txt
このcat
コマンドの結果をそのままls
に渡すには、コマンド置換を使います。
$ ls -l `cat list.txt`
-rw-r--r-- 1 project__personal-id project 4 4 1 11:16 1.txt
-rw-r--r-- 1 project__personal-id project 0 4 1 14:29 2.txt
-rw-r--r-- 1 project__personal-id project 84 4 1 15:49 3.txt
バッククオート`
もしくは$()
の中に記述されたコマンドは、先にシェルによって解釈され、その結果に置き換えられてから、本来のコマンドが実行されます。
$ ls -l $(cat list.txt)
-> cat list.txt が実行され、その結果で$()が置き換えられる
ls -l 1.txt 2.txt 3.txt
-> lsコマンドが実行される
-rw-r--r-- 1 project__personal-id project 4 4 1 11:16 1.txt
-rw-r--r-- 1 project__personal-id project 0 4 1 14:29 2.txt
-rw-r--r-- 1 project__personal-id project 84 4 1 15:49 3.txt
ヒント
厳密にはcat list.txt
の結果はスペース区切りではなく行区切りで出力されていますが、改行も空白文字なため、スペース区切りと同じように評価されます。
クォートすることで本来は行区切りなことを確認できます。
$ echo $(cat list.txt) # echoの第1〜3引数に、1.txt, 2.txt, 3.txtが渡される
1.txt 2.txt 3.txt
$ echo "$(cat list.txt)" # echoの第1引数に、"1.txt\n2.txt\n3.txt"が渡される
1.txt
2.txt
3.txt
7.1.1. 例¶
ファイル名に今日の日付をつける。
$ command 2> $(date "+%Y%m%d").log
$ ls
20220401.log
7.2. プロセス置換¶
あるコマンドの結果を、別のコマンドにファイルとして渡したい場合があります。例えば、2つのディレクトリに共通して存在する・しないファイルの確認は、次のようなコマンドで行えます。
$ ls
dir-a dir-b
$ ls dir-a > filelist-a.txt
$ ls dir-b > filelist-b.txt
$ sdiff filelist-[ab].txt
1.txt <
2.txt 2.txt
3.txt 3.txt
5.txt | 4.txt
この方法ではls
コマンドの結果をファイルに出力する必要がありますが、プロセス置換を使うとコマンドの結果を直接ファイルのように渡すことができます。
$ sdiff <(ls dir-a) <(ls dir-b)
1.txt <
2.txt 2.txt
3.txt 3.txt
5.txt | 4.txt
プロセス置換では<()
の中に記述されたコマンドを、入力ファイルに置き換えます。また、出力ファイルを指定できるコマンドの場合、ファイル名の代わりに>()
と書くことで、ファイルに出力されるデータを括弧内のコマンドの標準入力に渡すことができます。
7.2.1. 例¶
圧縮ファイルに対応していないプログラムにデータを解凍しながら渡す。
$ bwa mem index/hg38 <(zcat reads.fastq.gz)
tee
コマンドで同じデータを複数のコマンドに渡す。
$ cat script.sh | tee >(grep '^#' > comments.txt) | grep -v '^#' > codes.txt
ヒント
プロセス置換で渡されるデータは、シーク(データの読み直し)に対応していません。例えば、一度入力ファイルを最後まで読み込んでから、ファイルの先頭から再度データを処理するようなプログラムにプロセス置換で入力をした場合、プログラムがエラーになる場合があります。
ヒント
ハイフン-
が標準入力を表すことを利用して、パイプでプロセス置換を代用できることがあります。
$ ls dir-a | sdiff - <(ls dir-b) # sdiff <(ls dir-a) <(ls dir-b) と同じ
7.3. 算術演算¶
コマンドライン上で簡単な計算を行う場合はexpr
コマンドが使えます。
$ expr 1 + 2
3
ただし、引数として区別するために全ての演算子はスペースで区切る必要がある、*
のような演算子はクォートが必要となるなど制約があります。
そこで、bash
では$((expression))
という表記がサポートされています。expression
に式を記述することで、計算結果を展開してコマンドを実行します。
$ echo $(( 1 + 2 ))
3
$ echo $((2*3)) # クォートやスペース区切りが不要
6
なお、expr
やbashの$((...))
では整数の計算のみサポートされています。小数点演算が必要な場合は、bc
コマンドやawk
などのスクリプト言語を使用します。
$ echo 4 | awk '{print $1 / 0.7}'
5.71429
7.4. シェルにおける変数¶
多くのプログラミング言語と同じように、シェルでも変数を扱うことができます。
変数(シェル変数)は次のように書くことで定義されます。
NAME=value
変数名には英数字とアンダーバー(_
)が使えます。ただし、数字から始まる変数名は定義できません。
変数は、コマンド内で$
記号を使うことで参照します。
$ TEST_VAR=test
$ echo $TEST_VAR
test
また、変数名を中括弧で囲むこともできます。変数であることを強調したい場合や、文字列の中で変数を使いたい場合に用います。
$ PATH_PREFIX=data/exp1
$ echo "${PATH_PREFIX}_sample1.txt" # `{}`がないと`PATH_PREFIX_sample1`までが変数名になる
data/exp1_sample1.txt
代入(変数の書き換え)も同じように書くことで行なえます。言い方を変えると、既に使われている変数名を使った場合、その変数は上書きされます。
$ TEST_VAR=test1
$ echo $TEST_VAR
test1
$ TEST_VAR=test2
$ echo $TEST_VAR
test2
シェルではコマンドや引数の区別にスペースが使われているため、イコールの前後にスペースを入れてはいけないことに注意が必要です。また、空白文字等を含む文字列を変数に代入する際はクォートが必要です。
$ TEST_VAR=test # 正しい
$ TEST_VAR = test # `TEST_VAR`がコマンド名だと解釈される
bash: TEST_VAR: command not found
$ TEST_VAR="value with spaces" # 正しい
$ TEST_VAR=value with spaces # `with`コマンドに対する一時的な変数代入(後述)だと解釈される
bash: with: command not found
7.4.1. 環境変数¶
現在定義されている変数の一覧は、set
コマンドで確認することができます。
$ TEST_VAR=test
$ set
BASH=/bin/bash
BASH_ARGC=()
BASH_ARGV=()
...
TEST_VAR=test
...
set
コマンドを実行すると、シェルではいくつもの変数が予め定義されていることがわかります。
変数の中には、コマンドの挙動を変化させるものがあります。例えばLANG
変数は現在の言語を設定する変数ですが、日時を出力するdate
コマンドのデフォルトフォーマットは、この言語設定で変化します。
$ echo $LANG
ja_JP.UTF-8
$ date
2022年 4月 1日 金曜日 00:00:00 JST
$ LANG=C
$ date
Fri Apr 1 00:00:00 JST 2022
LANG
のような変数は、シェルの中で実行されるコマンド(プロセス)にも引き継がれます。例えば、シェルの中でさらに新しくシェルを立ち上げてみると確認することができます。
$ TEST_VAR=test # 変数を定義
$ echo $TEST_VAR
test
$ echo $LANG
ja_JP.UTF-8
$ LANG=C # `LANG`を書き換え
$ bash # 新しいシェルを立ち上げる
$ echo $LANG # 新しいシェルでは`LANG`を書き換えた結果は引き継がれている
C
$ echo $TEST_VAR # 変数`TEST_VAR`は引き継がれていない
$ exit # 立ち上げたシェルを抜ける
exit
$ echo $TEST_VAR
test
このように、シェルの変数には、あるシェルの中でしか参照できない変数と、シェルの中で実行したコマンドにも引き継がれる変数の2種類があります。前者をシェル(ローカル)変数、後者を環境変数と呼びます。環境変数は環境設定の定義や、何らかの情報を子プロセスに伝達したい場合に用いられます。
環境変数は、env
もしくはprintenv
コマンドで確認することができます。
$ env
SHELL=/bin/bash
WINDOWID=nnnnnnn
GTK_IM_MODULE=ibus
...
7.4.2. 主な環境変数¶
よく参照される環境変数の一部を列挙します。これらの変数は、コマンドやシェルそのものの動作を制御していることもあるので、同じ名前の変数を定義しようとして上書きしないよう注意が必要です。
変数名 |
説明 |
EDITOR |
|
HOME |
ホームディレクトリのパス |
LC_ALL, LANG |
ロケールを設定する |
LESS |
|
LOGNAME |
ログインしているユーザーのユーザー名 |
MANPATH |
|
PAGER |
|
PATH |
シェルがコマンド名からコマンドを探すときに参照するディレクトリのリスト。
PATH に挙げられたディレクトリに存在する実行可能ファイルは、
フルパスで指定しなくても実行できるようになる。 |
PWD |
カレントディレクトリのパス |
SHELL |
現在使っているシェルのプログラムへのパス |
http_proxy |
http接続時のプロキシサーバーを指定 |
https_proxy |
https接続時のプロキシサーバーを指定 |
7.4.3. 環境変数の操作¶
export
コマンドを使うことで、シェル変数を環境変数にすることができます。
$ TEST_VAR=test
$ export TEST_VAR
$ export TEST_VAR2=test2 # 定義もしくは代入も可
$ bash
$ echo $TEST_VAR $TEST_VAR2
test test2
環境変数のシェル変数への格下げは、export -n
で行えます。
7.4.4. 一時的な変数設定¶
コマンドの前に変数代入を記述することで、そのコマンドの実行中だけ変数を変えることができます。
$ echo $LANG
ja_JP.UTF-8
$ LANG=C date
Fri Apr 1 00:00:00 JST 2022
$ echo $LANG # 変数は変更されない
ja_JP.UTF-8
$ LANG=C man ls # 英語のマニュアルを見る
$ SLURM_ARRAY_TASK_ID=2 ./array.sh # 自作のスクリプトのテストをする
7.5. エイリアス¶
エイリアス(alias) を使うと、頻繁に使うコマンドと引数の組み合わせを別のコマンド名として登録することができます。エイリアスの登録はalias
コマンドを使います。
設定を永続化するには、シェル環境のカスタマイズもご覧ください。
$ ll
bash ll: command not found
$ alias ll='ls -l --color=auto' # スペースが含まれるのでクォートする
$ ll
合計 4.0k
drwxrwxr-x 2 project__personal-id project 22 4月 1 12:34 data
-rw-rw-r-- 1 project__personal-id project 118 4月 1 14:56 process.log
$ ll data/ # 引数の追加も可能
合計 4.0k
-rw-rw-r-- 1 project__personal-id project 898 4月 1 12:34 info.txt
$ alias rm='rm -i' # 同名のコマンドよりもエイリアスが優先される
$ rm process.log
rm: 通常ファイル `process.log' を削除しますか?
$ alias # 設定されているエイリアスを表示
ll='ls -l --color=auto'
rm='rm -i'
7.6. ロケール¶
ロケール(locale) は言語や国・地域に関する設定の総称です。Linux環境における言語設定と捉えて差し支えありませんが、言語の他にも、日時の表記法や使われる単位なども含む点が単なる言語設定と異なります。
ロケールは、LC_ALL
やLANG
のような環境変数に特定の値を指定することで設定できます。また、ロケールに関する環境変数はlocale
コマンドで確認することができます。詳しくはman 7 locale
をご覧ください。
7.7. シェル環境のカスタマイズ¶
CLI環境は、環境変数やエイリアス設定して環境をカスタマイズすることができますが、これらをシェル内で設定しても、次回以降にシェルを起動した際には引き継がれません。このような設定を永続化するには、特定のファイルに設定を記述します。
7.7.1. .profile¶
.profile
はホームディレクトリに置かれているシェルの設定ファイルです。このファイルに書かれた内容がログイン時に実行されます。
例えば環境変数を変更するときはここに記述します。
ヒント
ファイル名が.
から始まるファイルは隠しファイルです。
# .profile
# Get the aliases and functions
if [ -f ~/.bashrc ]; then
. ~/.bashrc
fi
# User specific environment and startup programs
PATH=$PATH:$HOME/local/bin # 例:`PATH`にディレクトリを追加
export PATH
注釈
/etc/profile
が存在する場合は、~/.profile
よりも先に読み込まれます。
ヒント
bashの設定ファイルとしては.bash_profile
というファイルもよく使用されます。.profile
はbash以外のシェルが起動した場合も設定ファイルとして読み込まれますが、.bash_profile
はbashでのみ有効です。また、.bash_profile
が存在する場合、bashは.profile
を読み込みません。
ToMMoスーパーコンピューターでは、.profile
が用意されているため、.bash_profile
向けの設定を記述する場合は、.profile
に記載するか、最初に.profile
をコピーして.bash_profile
を作成することをおすすめします。
7.7.2. .bashrc¶
.bashrc
もホームディレクトリに置かれている設定ファイルです。.profile
との違いは、.bashrc
はシェルが起動するたびに(例えばシェルの中でシェルを立ち上げた時にも)実行されます。エイリアスや、シェルだけが参照する変数はここに記述します。
# .bashrc
# Source global definitions
if [ -f /etc/bashrc ]; then
. /etc/bashrc
fi
# User specific aliases and functions
alias rm='rm -i' # 例:エイリアスを設定する
注釈
/etc/bashrc
や/etc/bash.bashrc
が存在する場合は、~/.bashrc
よりも先に読み込まれます。
7.8. 特殊なファイル¶
Linuxにおけるファイルはほとんどの場合、通常のファイルかディレクトリですが、中には特殊なファイルも存在します。
7.8.1. シンボリックリンク¶
シンボリックリンクは、Windowsにおけるショートカットです。例えば、/bin/sh
はシンボリックリンクになっています。
$ ls -l /bin/sh
lrwxrwxrwx 1 root root 4 Aug 24 2021 /bin/sh -> dash
1列目の1文字目のl
はシンボリックリンクであることを示します。/bin/sh -> dash
という表記は、シンボリックリンクが同じディレクトリにあるdash
を指し示していることを表します。このとき、/bin/sh
に対する読み込み・書き込み・実行は、実際には/bin/dash
というファイルに対して行われます。
シンボリックリンクの作成はln
(link) コマンドで行います。
ln -s source_file target_file
cp
コマンドやmv
コマンドと同じく、target_file
がディレクトリであれば、source_file
を複数指定することもできます。
7.8.2. /dev/null¶
Linuxには、デバイスファイルと呼ばれる、デバイス(ハードウェア、ただし仮想の場合もある)に紐付けられたファイルのようなものがあります。これらは/dev
というディレクトリの中に格納されていますが、その中でも一般ユーザーの使用頻度の高いデバイスファイルが/dev/null
です。
/dev/null
は、読み込んでも何も返さず、また書き込んだデータは全て捨てるようになっています。これは入力として空のファイルの代わりに使ったり、大量のデータを出力するコマンドのテストに有用です。
$ cat /dev/null # 何も返さない
$ echo something > /dev/null
$ cat /dev/null # 何かを書き込んでも変化しない
$ bwa mem genome/hg19 test.fastq > /dev/null # テスト用コマンドの標準出力を捨てる
$ ls nosuchfile.txt 2> /dev/null # エラーを無視する
$ ls nosuchfile.txt 2>&1 /dev/null # 標準出力・標準エラー出力を捨てる(例えば終了ステータスだけを見る場合)
7.9. xargsによる並列実行¶
xargs
コマンドは、標準入力から受け取った値を、指定されたコマンドに引数として与えて実行するコマンドです。コマンド置換でも同じことができますが、xargsでは同じコマンドを並列して実行できるため、同時実行数の指定ができないようなコマンドでも容易に並列実行させることができます。
xargs [options] command [args...]
- -a <file>, --arg-file=<file>¶
標準入力でなくファイルから引数のリストを読む
- -d <delim>. --delimiter=<delim>¶
引数リストの区切り文字に
<delim>
を使う
- -I <replace-str>¶
<replace-str>
で指定した文字列を引数で置き換える。-L 1
オプションが適用される。
- -L <max-lines>¶
引数リストから、
<max-lines>
行ずつ引数として渡す
- -n <max-args>, --max-args=<max-args>¶
引数リストから、
<max-lines>
個ずつ引数として渡す
- -P <max-procs>, --max-procs=<max-procs>¶
同時に
<max-procs>
個のプロセスを並列実行する(デフォルトは1)。-n
もしくは-L
オプションの指定が必要。危険
CPUコア数より多く指定するとパフォーマンスが低下します。また、ログインノードでの実行は 他のユーザに悪影響がないよう、並列数を慎重に指定してください。
- -p, --interactive¶
各コマンドの実行前にユーザーに尋ねる
$ ls
data.txt
$ cat files.txt
1.txt
2.txt
3.txt
$ cat files.txt | xargs --verbose -n 1 cp data.txt # 引数を末尾に1つ追加して`cp`を3回実行
cp data.txt 1.txt
cp data.txt 2.txt
cp data.txt 3.txt
$ cat files.txt | xargs --verbose ls # デフォルトでは全ての引数が渡される
ls 1.txt 2.txt 3.txt
1.txt 2.txt 3.txt
$ mkdir data
$ cat files.txt | xargs --verbose -I XXX mv XXX data/
mv 1.txt data/
mv 2.txt data/
mv 3.txt data/
$ ls *.fastq.gz | xargs -P 4 -n 1 md5sum > checksum.txt # 時間のかかるコマンドを4並列で実行