8
19

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

シェル&シェルスクリプト チートシート

Last updated at Posted at 2019-04-24
  • ここに書いてあることは基本的に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行ごとにタイムスタンプをつける

timestamp.sh
#!/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 ' '

例:
スクリーンショット 2019-12-24 3.45.07.png

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 
8
19
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
19

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?