#
ドキュメント

Document

自分のための備忘録です。

シェル

ベストプラクティス

  • パラメータ(変数)参照は必ずダブルクォートで囲む "$var"
  • コマンド置換 $(command) は必ずダブルクォートで囲む "$(commnad)"
  • シェル変数は小文字、環境変数は大文字で定義する

本ドキュメントは上記に準拠していません。

コマンドの文法

ref. https://qiita.com/ko1nksm/items/9650ed1fc21d668f2732?utm_source=pocket_mylist

# コマンドはプリフィックスの0回以上の繰り返し コマンド名 サフィックスの0回以上の繰り返しで構成される
$ [プリフィックス]... コマンド名 [サフィックス]...
  • プリフィックス:変数代入 or リダイレクト
    • 変数代入はコマンドにコマンド内でのみで有効な環境変数を渡す
  • サフィックス:引数 or リダイレクト

プレフィックスによる変数代入

変数代入はコマンドに環境変数を渡す処理です。
※ 例は env コマンドを省略していますが、すべての環境で省略できるわけではありません( Mac の Zsh や Bash では省略可能)。

$ date
2021年 11月 8日 月曜日 20時00分26秒 JST

$ LANG=C date
Mon Nov 8 20:01:54 JST 2021

$ date
2021年 11月 8日 月曜日 20時03分17秒 JST

https://qiita.com/ko1nksm/items/9650ed1fc21d668f2732?utm_source=pocket_mylist

LANG=C date # 一時的な変数代入
# 厳密にはシェル関数の場合は・・・と長い話があるのですが省略します
LANG=C # 恒久的な変数代入

https://qiita.com/ko1nksm/items/9650ed1fc21d668f2732?utm_source=pocket_mylist

Linuxのローカライゼーション系LANG変数:langについて

サフィックスによるリダイレクト

$ > /tmp/ls.txt la -al
# 以下と等価
$ ls -al > /tmp/ls.txt

プロセス

  • コマンド実行からアプリケーション実行まで、すべての処理がプロセス単位で実行される。

詳細はlinuxを参照。

サブシェル

サブシェルとは、シェル (sh や csh など) から起動された子プロセスのシェルのこと。

ref. http://x68000.q-e-d.net/~68user/unix/pickup?%A5%B5%A5%D6%A5%B7%A5%A7%A5%EB

サブシェルはシェルから起動した子プロセスのことです。
サブシェルの特徴をまとめます。

  • カレントシェル^main-shellはサブシェルの実行が終わるまで待機する
  • サブシェルはカレントシェル(親シェル)のシェル変数を引き継がない
  • サブシェルのシェル変数はカレントシェル(親シェル)に影響しない
  • サブシェルはカレントシェル(親シェル)の環境変数をを引き継ぐ
  • サブシェルの環境変更はカレントシェル(親シェル)に影響しない
    カレントシェル(親シェル)の環境変数をサブシェルで変更してもカレントシェルの環境変数は変更されない

ref. 入門UNIXシェルプログラミング(p26)

シェルスクリプトの実行とサブシェル

カレントシェルでスクリプトを実行するか、サブシェルで実行するかはスクリプトの実行方法によってきまります。

  • パイプ |:サブシェルで実行
  • $ sh script.sh:サブシェルで実行
    ※ "c"オプションは「コマンドを指定された文字列から読み取る」 ref. https://news.mynavi.jp/techplus/article/techp4484/
  • $ ./script.sh:サブシェルで実行(実行権限付与済み $ chmod u+x script.sh
  • $ . ./script.sh:カレントシェルで実行
  • $ source script.sh:カレントシェルで実行( . コマンドと異なり相対パスまたは絶対パスで指定する必要がない)
  • exec コマンド: $ exec {{command}}:不明

カレントシェルで実行(例 . ./script.sh や source script.sh )

子プロセスを作成せずにカレントシェルのプロセスでスクリプトを実行します。
スクリプト実行後にカレントシェルで script.shで定義した変数(環境変数シェル変数とも)を使用できます。

サブシェルで実行(例 sh script.sh や ./script.sh )

子プロセスでスクリプトを実行します。そのためスクリプト実行後に、カレントシェルで script.sh で定義した変数(環境変数シェル変数とも)を使用できません。

# script.sh
hello="hello"
echo "$hello"
$ sh ./script.sh
hello
$ echo "$hello"
// 何も表示されない

typeコマンド

type コマンドはコマンドの種類を表示します。

a オプション:一致したすべてを表示する

$ type -a echo
echo is a shell builtin
echo is /bin/echo

コマンドの種類は以下のとおりです。

  • エイリアス
  • 関数( function )
  • シェルの組み込み関数( shell builtin )
  • キーワード( keyword )
  • ファイル( file )

変数

基本

ポイントをまとめます。
  1. = を使う
  2. 値の設定時は変数名の先頭に $ を付けない ($ は参照時のみ)
  3. = の前後にスペースを入れない (前後にスペースがあるとエラー)
  4. 型は存在しない (declare コマンドや typeset コマンドで似たようなことができるが推奨はしない)
  5. 変数名の大文字・小文字は区別される ( VAR と var は別物)

特に「2」は初心者が犯しがちなミスなので注意すること。

ref. 変数を使用する | UNIX & Linux コマンド・シェルスクリプト リファレンス

特殊なシェル変数

$0 # シェルスクリプト名
$1~$9 # 1番目~9番目の引数
$* # すべての引数(詳細後述)
$@ # すべての引数(詳細後述)
$# # 引数の数
$? # 直前に実行したコマンドの終了ステータス。0は成功、0以外は失敗
$- # シェルの実行オプション (/bin/bash -opt)
$$ # シェルのプロセスID
$! # 最後に実行したバックグランドプロセスのプロセスID
$_ # 最後に実行したコマンドの最後の引数

引用元:https://www.tohoho-web.com/ex/shell.html#special

環境変数

環境変数の作成

# 以下では環境変数として設定されない実装がある( Mac の zsh では環境変数として設定される)
$ FOO=BAR
$ export FOO
$ env | grep FOO
// 空

# 以下で環境変数として設定される
$ export FOO=BAR
$ env | grep FOO
// FOO=BAR

# 以下でも環境変数として設定される
$ FOO=BAR export FOO
$ env | grep FOO
// FOO=BAR

環境変数の削除

$ unset FOO

printenvコマンド

環境変数を表示します。

// 環境変数一覧を表示
$ printenv
// 指定した環境変数の値を表示
$ printenv SHELL
/bin/bash

env コマンドを引数なしで実行しても環境変数一覧を取得できます。

$ env
// 環境変数一覧を表示( printenv とほぼ同等)

env 本来のユースケースは、コマンド実行時に一時的に環境変数をコマンドに渡すことです。

// date
// 2022年 1月22日 土曜日 10時25分27秒 JST  <--- 変更前
$ env LANG=c date
Sat Jan 22 10:25:55 JST 2022              <---- 一時的に変更
// 一時的に環境変数を設定するのでコマンド実行ごはもとに戻る
// $ date
// 2022年 1月22日 土曜日 10時26分06秒 JST      <--- もとに戻る

Zsh や Bash では env は省略可能ですが独自使用なので注意が必要です。

# これでも大丈夫
$ LANG=c date
Sat Jan 22 10:25:55 JST 2022

シェル変数

シェル変数は set コマンドで一覧表示する。

$ set
// すべてのシェル変数を表示
$ Foo="Bar"
// シェル変数定義
$ set | grep Foo
// Foo=Bar
$ printenv | grep Foo
// 結果なし
$ export Foo
$ printenv | grep Foo  # 環境変数になる
// Foo=Bar

ref.
https://qiita.com/chihiro/items/bb687903ee284766e879

コマンドとシェル組み込み関数

echoやprintfのように、同じ名前で実行ファイルで存在しているコマンド(ex:/bin/echo)と、シェルの組み込みとして存在しているコマンドがあったりする。 シェルスクリプト上ではパスを指定しないと組み込みのものが使われる。機能に差異があったりするので、実行ファイルのほうを使う場合は明示的にパスを指定するなどして区別する。

ref. https://qiita.com/Ping/items/57fd75465dfada76e633

ワイルドカード

シェルのワイルドカードと正規表現やプログラム言語固有のワイルドカードの違いに把握します。

  • *:任意の0以上の文字列(0文字以上でも良いことに注意)
  • ?:任意の1文字

* と glob 機能

$ echo *
// カレントディレクトリのファイルを表示する
README.html test1.txt test2.txt
$ echo "*"
// * が表示(glob 機能が無効化される)

クォート

  • シングルクォート( '' ):文字列を表す(パラメータ(変数)を展開しない)
  • ダブルクォート( "" ):文字列を表す(パラメータ(変数)を展開する)
  • バッククォート( `command` ):中身をコマンドとして実行(コマンド置換)
    $(command) も command を実行する。$(command)のほうが視認性もよくネストも可能なので、通常 $(command) を使用する
文字 内容
' すべてキャラクタがエスケープされます。
" $ で変数を展開します。$, `,\, "はエスケープする必要があります。
$ nowTime=2021-01-01
$ echo '時刻:${nowTime}'
// 時刻:${nowTime}
$ echo "時刻:${nowTime}"
// 時刻:2021-01-01
$ echo "時刻:`date`"
// 時刻:Sat 23 Oct 2021 12:38:37 PM UTC
$ echo "時刻:$(date)"
// 時刻:Sat 23 Oct 2021 12:38:37 PM UTC

シングルクォートで ' をエスケープ

文字列詳細

echo Hello World
// Hello World # クォートで囲まなくても表示
echo 'Hello' World
// Hello World # 文字列の連結に演算子は必要ない
MY_NAME='Hiroshi Sawai'
echo $MY_NAME
// Hiroshi Sawai
echo ''
// なにも表示されない
echo 
// シングルクォート内のシングルクォートをエスケープする方法はない
// エスケープできない
echo '\''
quote>
// 'は表示できない
// 以下のようにシングルクォートの外ではエスケープで表示できる
 $ echo \'foo\'
// foo

$(command) コマンド置換

$(command) はバッククォートと同じように command を実行する。
ただしネストできるなどバッククォートより高機能。

$ echo $(date -d @$(date +%s))
// 数字の前に @ を付けると、UNIX時間での指定になる。ref. https://hydrocul.github.io/wiki/commands/date.html
// date +%s はUnixタイムスタンプを出力
// Sat 23 Oct 2021 12:49:17 PM UTC

コマンド実行結果を変数に代入

`{{command}}`か$({{command}})で実行した結果を変数に代入します。

# 引用元:https://qiita.com/Ping/items/57fd75465dfada76e633
TODAY= "$(date +'%Y/%m/%d')"
echo "$TODAY"
NOWTIME="$(date +'%H:%M:%S')"
echo "$NOWTIME"

環境変数 PATH

環境変数 PATH の値を確認する。

$ echo "$PATH"

.bash_profile

パスを通すとは、シェル設定ファイル( .bash_profile.zshrc ) で環境変数 PATH にパスを追加すること。   export で環境変数(この場合は PATH )を更新する。

PATH=$PATH:/path/to/add
export PATH

PATH の TIPS

(1) PATH の優先順位を確認

$ cat /etc/paths
// 優先度の高い順に表示

例えば以下のように、 .bash_profile で優先順位を変更できます。

PATH=/path/to/dir:$PATH
export PATH

(2) source コマンド

.bash_profile を変更したときは、ログインし直さないと変更が反映されません。
source コマンドを使うとログインし直さなくても変更が反映されます。
. コマンドは source コマンドとほぼ等価です.

$ source /path/to/.bash_profile
// or
$ . /path/to/.bash_profile

本当に正しい .bashrc と .bash_profile の使ひ分け - Qiita

if 文

#!/bin/bash
#
# e エラー発生時にスクリプトを終了する
# u 未定義変数の使用をエラーにする

set -eu

var="abc"

# test command
if test "$var" = "abc"; then
    echo "same"
fi

# [ command
echo "=== [ command ==="

if [ "$var" = "abc" ]; then
    echo "same"
fi

# [[ keyword
# [[ keyword は Bash の独自拡張
# 正規表現 =~ などの機能が使用できる
echo "=== [[ keyword ==="

if [[ "$var" =~ ^a.+ ]]; then
    echo "match"
fi

# ( ) command substitution
echo "=== ( ) command substitution ==="

echo -e "aaa\nbbb\ccc" >tmp.txt
if (grep "bbb" tmp.txt >/dev/null); then
    echo "match"
fi

# (( )) arithmetic operation
echo "=== (( )) arithmetic operation ==="
num=5
if ((num < 10)); then
    echo "5 is less than 10."
fi

[ ... ] で使用できる表記

( exp ) # exp をグルーピング
! exp # exp が偽であれば
exp1 -a exp2 # exp1 かつ exp2 であれば
exp2 -o exp2 # exp1 または exp2 であれば
str1 = str2 # 文字列 str1 と str2 が等しければ
str1 != str2 # 文字列 str1 と str2 が等しくなければ>
-z str # 文字列 str が0文字であれば(zero)
-n str # 文字列 str が0文字で以上であれば(not zero)
str # 文字列 str が0文字で以上であれば(-n str と同じ)
num1 -eq num2 # 数値 num1 が num2 と等しければ(equal)
num1 -ne num2 # 数値 num1 が num2 異なっていれば(not equal)
num1 -ge num2 # 数値 num1 が num2 以上であれば(grater than or equal)
num1 -gt num2 # 数値 num1 が num2 より大きければ(grater than)
num1 -le num2 # 数値 num1 が num2 以下であれば(less than or equal)
num1 -lt num2 # 数値 num1 が num2 より小さければ(less than)
file1 -ef file2 # ファイル file1 が file2 と同一実体であれば(equal file)
file1 -nt file2 # ファイル file1 が file2 より新しければ(newer than)
file1 -ot file2 # ファイル file1 が file2 より古ければ(older than)
-e file # ファイル file が存在していれば
-s file # ファイル file が0バイト以上であれば
-f file # ファイル file がレギュラーファイルであれば
-r file # ファイル file が読み込み可能であれば
-w file # ファイル file が書き込み可能であれば
-x file # ファイル file が実行可能(ディレクトリの場合は移動可能)であれば
-d file # ファイル file がディレクトリであれば
-h file # ファイル file がシンボリックリンクファイルであれば(-Lと同義)
-L file # ファイル file がシンボリックリンクファイルであれば(-hと同義)
-b file # ファイル file がブロックデバイスファイルであれば
-c file # ファイル file がキャラクタデバイスファイルであれば
-p file # ファイル file が名前付きパイプであれば
-S file # ファイル file がソケットファイルであれば
-k file # ファイル file にスティッキービットが設定されていれば(chown o+t)
-u file # ファイル file にセットユーザIDビットが設定されていれば(chown u+s)
-g file # ファイル file にセットグループIDビットが設定されていれば(chown g+s)
-O file # ファイル file が実効ユーザIDに所有されていれば
-G file # ファイル file が実効グループIDに所有されていれば
-t fd # ファイルディスクリプタ fd がターミナルとして開かれていれば

引用元:https://www.tohoho-web.com/ex/shell.html#if-statemant

[[ ... ]]

DB="mysql";
if [[ "$DB" == "mysql" ]]; then
  mysql -u root myapp_test < "${TRAVIS_BUILD_DIR}/bin/infotownlinkwp.sql";
fi
if [[ "$DB" == "pgsql" ]]; then 
  psql myapp_test < "${TRAVIS_BUILD_DIR}/bin/infotownlinkwp.sql";
fi
  • [ ... ]は test の糖衣構文。... の前後に半角スペースが必要
  • [[ ... ]] は Bash 専用の言語構造で [ ] より高機能

for in

#!/bin/bash
total=0
for x in 100 200 300; do
    ((total = total + ("$x" * 2)))
done
echo $total

# expr is antiquated. (()) is best practice.
total=0
for x in 100 200 300; do
    total=$(expr "$total" + "$(expr "$x" \* 2)")
done
echo "$total"

while/read

// input.txt
abc
def
ghi
while read line
do
  echo "$line"
done < input.txt

標準入出力・標準エラー出力

ファイル・ディスクリプター

ファイル・ディスクリプター番号 入出力先
0 標準入力
1 標準出力
2 標準エラー出力
n 任意の入出力先

ref. https://qiita.com/laikuaut/items/e1cc312ffc7ec2c872fc

出力制御サンプル

output.sh

#!/bin/bash
# e エラー発生時にスクリプトを終了する
# u 未定義変数の使用をエラーにする

set -eu

echo 'standard output' >&1
echo 'standard error output' >&2
exit 1

※ スクリプトを実行するときは chmod u+x で実行権限を付与する。またサブシェルで実行しないとシェル自体が終了する(例 . path/to/script )

ディスクリプタが省略された場合は標準出力 1 を指定したことになる。

$ ./output.sh 1>/dev/null
standard error output   // <-- 標準エラーのみ表示。標準出力は /dev/null に出力。
// 以下と等価
$ ./output.sh >/dev/null
standard error output

& のみを指定した場合は標準出力 1 と標準エラー出力 2 を指定したことになる。

$ ./output.sh &>/dev/null
// 出力なし

リダイレクトを複数指定した場合は左から右に評価される。

$ ./output.sh 2>&1 >/dev/null
// standard error output
// 以下は↑と等価
$ ./output.sh 2>&1 1>/dev/null

標準出力 1 および標準エラー出力 2 ともに /dev/null に出力。

$ ./output.sh >/dev/null 2>&1
// 出力なし

set -eu

  • e: エラー発生時にスクリプトを終了する
  • u: 未定義変数の使用をエラーにする

https://qiita.com/youcune/items/fcfb4ad3d7c1edf9dc96

サンプル集

ディレクトリのファイル一覧を取得。

#!/bin/bash
for f in $(find . -maxdepth 1 -type f)
do
    echo "---"
    echo $f
done

※ "$(find . -maxdepth 1 -type f)" だと以下のように 1 度しかループされない。

ディレクトリ内のファイルサイズを取得。

#!/bin/bash
for f in  $(find . -maxdepth 1 -type f)
do
  wc -c < "$f"
done

ディレクトリサイズ表示。

#!/bin/bash
for d in  $(find . -maxdepth 1 -type d)
do
  du -h "$d"
done

文字列結合

#!/bin/bash

lines=''
while read line
do
   lines=$lines$line 
done < file.txt
echo $lines

file.txt

aaa
bbb
ccc

実行

$ ./script.sh
aaabbbccc

Ref