- ここに書いてあることは基本的にPOSIX compliant(のはず)
- POSIX compliantじゃないことはその旨記載
変数の参照
忘れがちなcase文
$var="YES"
case $var in
"Y" | "y" | "yes" | "Yes" | "YES" )
echo "YES!"
;;
* )
echo "NO!"
;;
esac
引数の処理
例:
while getopts f:h OPT; do
case $OPT in
"f" ) FLG_f="TRUE"; VALUE_f=${OPTARG} ;;
"h" ) echo "使い方: ${CMDNAME} [-f] LOGDIR"
echo " ${CMDNAME} [-h]"
echo " LOGDIR ログが保存されているディレクトリのパス"
echo " -h このヘルプを表示する"
exit 0 ;;
esac
done
shift `expr $OPTIND - 1`
log_dir="${1}"
if [ "${log_dir}" = "" ]; then
error '第1引数にログディレクトリのパスを指定してください'
fi
文字に色をつけたい
色をつける: \e[文字色2桁;背景色2桁m
戻す: \e[m
printf '\e[31mRED\e[m\n' # 赤
printf '\e[32mGREEN\e[m\n' # 緑
printf '\e[33mYELLOW\e[m\n' # 黄色
printf '\e[34mBLUE\e[m\n' # 青
printf 'NORMAL\n' # 元の色
ファイルから環境変数を読み込んで設定したい
source
を使うのではなく、単にNAME=val
の形式で環境変数の一覧が書かれているファイルから読み込んで環境変数を設定する方法。
パイプを使って cat ENV.txt | ...
とかやろうとするとパイプはサブプロセスで実行されてしまうので、for
でファイルを読み込む。
$ cat ENV.txt
URL=http://example.com/
$ for env in `cat ENV.txt`; do export "${env}"; done; sh -c 'echo ${URL}'
http://example.com/
エスケープシーケンスを楽に扱いたい
tput
を使う。(POSIX準拠コマンドではない)
from: https://qiita.com/onokatio/items/5d282b72ac5565ae4569
ランダムな文字列を得たい
from: https://qiita.com/yasuo-ozu/items/b57af1ffcb867347bab9
-
/dev/urandom
自体はPOSIXでは規定されてないっぽいです
数字だけ
cat /dev/urandom | LC_CTYPE=C tr -dc '0-9' | fold -w 32 | head -n 1
0~7の範囲の数字を1文字
cat /dev/urandom | LC_CTYPE=C tr -dc '0-7' | fold -w 1 | head -n 1
数字とアルファベット
cat /dev/urandom | LC_CTYPE=C tr -dc 'a-z0-9' | fold -w 32 | head -n 1
文字列を1文字ずつに分解したい
echo "aaaa" | fold -w 1 | tr '\n' ' ' | sed 's/ $//g'
簡単なエラーハンドリング
error() {
printf '\e[31mエラー: %s\n終了します\e[m\n' "${1}" 1>&2
exit 1
}
error '不明なエラー'
こういうのもいいかも
CMDNAME=`basename $0`
error() {
printf '\e[31m[%s] エラー: %s\e[m\n' "${CMDNAME}" "${1}" 1>&2
printf '\e[31m[%s] 終了します\e[m\n' "${CMDNAME}" 1>&2
exit 1
}
CMDNAME=$(basename $0)
error() {
printf '\e[31m%s: エラー: %s\e[m\n' "${CMDNAME}" "${1}" 1>&2
printf '\e[31m%s: 終了します\e[m\n' "${CMDNAME}" 1>&2
exit 1
}
set -eu するときのエラーハンドリング
#!/bin/sh
set -eu
error_handler() {
# エラー時の処理
}
cmdname=$(basename "${0}")
error() {
printf '\e[31m%s: エラー: %s\e[m\n' "${cmdname}" "${1}" 1>&2
printf '\e[31m%s: 終了します\e[m\n' "${cmdname}" 1>&2
exit 1
}
trap error_handler EXIT
# ここで通常の処理
# ここで通常の終了処理
# 異常終了時ハンドラの解除
trap '' EXIT
root権限で実行しているかどうかチェックする
if [ "$(id -u)" != "0" ]; then
echo "エラー: root権限が必要です" 1>&2
exit 1
fi
スクリプト自身のファイル名(コマンド名)を取得する
CMDNAME=$(basename $0)
出力1行ごとにタイムスタンプをつける
#!/bin/sh
while read -r l; do
printf '%s %s\n' "`date \"+%Y-%m-%dT%H:%M:%S%z\"`" "${l}"
done
some_command | ./timestamp.sh
ファイルに書き込み権限があるかどうかチェックする
test
コマンドで普通にできる
test -w path/to/file
または
[ -w path/to/file ]
スクリプトのパス(スクリプトのあるディレクトリ)の取得
dirname $0
標準出力/標準入力を流れているデータの速度を見たい
pv
コマンドを使う
参考: https://qiita.com/kitsuyui/items/549c8e786e7d456e0923
while read
で標準入力から読み込んでいる最中に端末からの入力を受け付けたい
read input < /dev/tty
1行を更新しつづけたい
while :; do
date +%s
sleep 1
printf '\e[1F\e[2K'
done
psの結果を毎秒見たい
- dateとpsの結果を受け取る
- 画面をクリアする
- dateとpsの結果を表示する
while :; do date="$(date)"; ps="$(ps -A -o pid,command | grep ffmpeg)"; clear; printf '%s\n----------------\n%s\n' "${date}" "${ps}"; sleep 1; done
カウントアップタイマー
st="$(date +%s)"; while :; do ct="$(date +%s)"; printf '%d' "$((ct-st))"; sleep 1; clear; done
[非POSIX] 出力結果をテーブル表示したい
column -t -s ' '
xargsを入れ子にしたい
xargsのパラメータには1コマンドしか渡せないっぽいので、sh -c 'command'
で入れ子にしたいコマンドを実行する。
ls | xargs -IXXX sh -c 'ls | xargs -IYYY mv XXX/YYY XXX/dst'
現在時刻をyyyymmdd HHMMSS
で取得したい
西暦4桁、時分秒を2桁で取得したい。
date "+%Y-%m-%d% %H:%M:%S"
ファイルのN行目以降を出力したい
例えば、file
の2行目以降を出力したい場合は、
tail -n +2 < file
月の最終日を求めたい
2020年の5月の最終日を求めるには、
cal 05 2020 | grep -v '^ *$' | tail -n 1 | sed 's/ *$//g' | sed 's/^.*\(..\)$/\1/'
文字列テンプレート
- evalは普通に怖い
-
fuga=" date; "
みたいな単純なインジェクションは大丈夫そうだけど、テンプレートにはコマンド書き放題だし、大丈夫なのかよくわからん - どうしても個別のファイルにしたいわけでなければ普通にヒアドキュメントを使ったほうがよさそう
$ printf 'あの${hoge}の${fuga}風\n' > template.txt
$ hoge="イーハトーヴォ"; fuga="すきとおった"
$ eval "echo $(cat template.txt)"
あのイーハトーヴォのすきとおった風
$ hoge="環七沿い"; fuga="排気ガス混じりの"
$ eval "echo $(cat template.txt)"
あの環七沿いの排気ガス混じりの風
- テンプレートエンジンを使いたいなら、mustacheのC実装であるmustachがよさそう
[非POSIX] 現在時刻と24時間後の時刻を表示する
utconv
自体はPOSIX shellがあれば動くぞ
now_t=$(date +%s); tomo_t=$((now_t + 86400)); printf 'now: %s %s\ntomorrow: %s %s\n' "${now_t}" "$(utconv -r ${now_t})" "${tomo_t}" "$(utconv -r ${tomo_t})"
[非POSIX] シェルスクリプトをステップ実行したい
そんな欲求が出てきた時点で行数を減らすことを考えたほうがいいのですが、bashdb
を使うとステップ実行ができる。
brew install
する場合は、手動でsymlinkをつくってやらないといけなかった。
あと、bashの--posix
がどの程度bourne shellと互換性あるのかはよくわからない。
brew install bashdb
mkdir /usr/local/Cellar/bash/5.0.18/share/bashdb # ここはバージョンによって変わるので注意
ln -s /usr/local/Cellar/bashdb/5.0-1.1.2/share/bashdb/bashdb-main.inc /usr/local/Cellar/bash/5.0.18/share/bashdb/bashdb-main.inc # ここはバージョンによって変わるので注意
bash --posix --debugger hello.sh