警告

本資料は参考情報として提供されています。内容についてのご質問には必ずしもお答えできない可能性がございます。

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

lessコマンドなどで編集時に使用するテキストエディタを指定

HOME

ホームディレクトリのパス

LC_ALL, LANG

ロケールを設定する

LESS

lessコマンドに自動で渡す引数を指定

LOGNAME

ログインしているユーザーのユーザー名

MANPATH

manコマンドがマニュアルを探すディレクトリを指定

PAGER

manなどでテキストを表示する際に使用するコマンドを指定

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_ALLLANGのような環境変数に特定の値を指定することで設定できます。また、ロケールに関する環境変数は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

各コマンドの実行前にユーザーに尋ねる

-t, --verbose

標準エラー出力に実行するコマンドを出力する

$ 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並列で実行