Help us understand the problem. What is going on with this article?

Bashの便利な構文だがよく忘れてしまうものの備忘録

Bashでスクリプトを書く場合のポイントをまとめた

Bashなどで書くシェルスクリプトは文法がシンプルで覚えると便利。他のプログラム言語だと何行もかかなければならないファイル操作や中身のデータ処理を、ちょちょいと数行レベルで書けとても強力だ。コマンドとの親和性が高い恩恵だろう1

旧来バッチ処理で本領を発揮するシェルスクリプトだったが、昨今はDocker(Dockerfileや起動スクリプトなど)で活用される機会も多いだろう。
しかしながら、他のプログラム言語ではみられない記法があったり、逆に似た書き方もあったりと、混乱しがちなのもシェルスクリプトの特徴かもしれない。

いちいちGoogle先生に確認するのもめんどうなので、このページに整理した2

主な参照先(よくお世話になっているページ):

記法・特殊変数

基本

  • そんな高級な言語仕様じゃないし、わりとデバッグやテストもしにくい。
    • でも、他のプログラム言語だとコードを何行も書かなければならない処理も、コマンド数発とパイプのワンライナーでいけたりとか、その爽快感は魅力。
  • 一部の言語にあるような命令後の;(セミコロン)は、要らない。ただし1行に複数命令を各場合は、明示的;で区切る。
  • コメントは#を用いる。#以降が無視される。
  • if ~ fi とか case ~ esac とか、他の言語じゃ見られないような、洒落た文法。
  • 括弧はいろいろな文法を表現できるようになっている。別記事にまとめてみたので、ご参照あれ。 - Bashの括弧のノウハウ(まとめ)

シェバング

スクリプト行頭の#!で始まる行をシェバング(シェバン、シバンとも)といい3、なんのプログラムで実行すべきかを指し示すことができる。シェルスクリプトの場合は、次のような形になるだろう。

#!/bin/sh
#!/bin/bash

スクリプトファイルに実行パーミッションを設定して直接実行すると、シェルはこの1行目を見て該当するコマンドを用いてスクリプトファイルを実行する。

ほとんどのLinuxのshbashへのリンクとなっている4が、シェバングがshで実行されようとしている場合には、Bashはできるだけshの動作をしようとする。その結果Bash独自の記法が一部使えなくなるので注意だ。(こちらの解説が参考になる。)

特にこだわりがなければ、#!/bin/bash とするのが無難かもしれない。

shスクリプト
#!/bin/sh
grep 'fuga' < <(cat hoge.txt)    # エラー。sh は <() を解釈できない。
bashスクリプト
#!/bin/bash
grep 'fuga' < <(cat hoge.txt)    # OK. Bashの構文として解釈される。

変数・文字列

変数と代入

任意の文字列を使った変数が作れる5。参照時には$をつけるが、代入する際は$を使わない。代入は=を用いる。
右辺式が文字列の時で、(途中に空白などがなく)式として成り立っていればクォーテーションしなくても代入が可能。
=の前後に可読性目的で空白を入れてはいけない。言語的にエラーである。
他のスクリプト言語のように${VALNAME}といった形で{}で囲むことでき、明示的に文字列と区別させることが可能。

GREETING='Hello! World!!'
MYNAME=HAL3
NOW=`date +'%Y/%m/%d %H:%M:%S'`
echo "${MYNAME}_$NOW> $GREETING"
# HAL3_2018/01/12 12:34:56> Hello! World!!

文字列連結とクォーテーションマーク

文字列連結

連結するには演算子などは必要なく、例のように繋げて記述すれば良い。繋げる文字列の間での空白は不要。

$ HOGE="hoge"
$ FUGA="fuga"
$ echo $HOGE$FUGA      # hogefuga
$ echo "hoge""fuga"    # hogefuga

ダブルクォーテーションマーク

ダブルクォーテーションマークで囲んだ文字列では、式が展開される6

$ NUM=42
$ echo "THE ANSWER IS $NUM"           # THE ANSWER IS 42
$ echo "my HOSTNAME is `hostname`"    # my HOSTNAME is xxx

シングルクォーテーションマーク

シングルクォーテーションマークで囲んだ文字列では、式は展開されない6
!!!などはBashはヒストリを参照する時の特殊コマンドになるので、これを文字列に埋め込むにはシングルクォーテーションで囲むなどしてエスケープする必要がある。

$ echo 'THE ANSWER IS $NUM'          # THE ANSWER IS $NUM (展開されない)
$ echo 'Hello!!'                     # ""だと、 !! が直前のコマンドに置き換わる

文字列の中へのクォーテーションマークの展開

クォーテーションマークの中で、別なクォーテーションマークをエスケープなしで記述が可能である。

$ BOO='TSUNODA "STAR" HIRO'
$ echo $BOO                          # TSUNODA "STAR" HIRO
$ POO="NISHIKINO 'STAR' AKIRA"
$ echo $POO                          # NISHIKINO 'STAR' AKIRA      
$ STAR="☆"
$ perl -e 'print qq(LUCKY '"'"$STAR"'"' STAR\n)'  # LUCKY '☆' STAR

位置パラメータ

スクリプト実行時に引数として渡された内容は、位置パラメータと呼ばれる特殊変数に保持される。これらはスクリプト中では、$1$9で表わせる。
10番目以降の位置パラメータは、${10}のように記述する。
なお、位置パラメータは関数への引数を展開する場合にも使う。

$ piyopiyo.sh "hoge" "fuga" 3 4 5 6 7 8 9 ten eleven
# $1    => hoge
# ${10} => ten

$*, $@, "$@" の違い

位置パラメータを集合的に扱う場合は、$*, $@, "$@"を利用する。
クォーテートしない$@$* と同じだ。なので実質$*"$@"を使い分ければ良い。

sample.sh
echo '--- $* ---'; for P in $*; do echo $P; done
echo '--- $@ ---'; for P in $@; do echo $P; done        # $* と同じ
echo '--- "$@" ---'; for P in "$@"; do echo $P; done
$ ./sample.sh "1 2" "hoge fuga"
--- $* ---
1
2
hoge
fuga
--- $@ ---
1
2
hoge
fuga
--- "$@" ---
1 2
hoge fuga

Bashの変数展開時の置換

Bashの便利機能。たくさんある。こちらのQiitaページに詳しく書かれている

bash
echo $HOGE            # (null)
echo ${HOGE:-hoge}    # hoge    $HOGEがNULLの場合に"hoge"に置き換える。代入はされない。
echo $HOGE            # (null)

echo $FUGA            # (null)
echo ${FUGA:=fuga}    # fuga    $FUGAがNULLの場合に"fuga"に置き換え、かつ、代入もする。
echo $FUGA            # fuga
bash
# change suffix of files
for F in *.JPG;do mv $F ${F//.JPG/.jpg};done;
# cut matched string at head of value
while read URL;do echo ${URL#http://};done < urls.txt
# cut matched string at foot of value
for F in *.tmp;do echo ${F%.tmp};done
# cut matched string as long as pattern can from head
myfilename=${0##*/}    # same as basename

配列

Bashでは配列が使える。
使い方については下記ページの解説が詳しいので、ここでは割愛する。(個人的にあまり使ったことがないというのもあるので…)
Qiita - bash 配列まとめ

コマンド実行

任意のコマンドは、スクリプトファイルで記述して実行したり、CLI上で実行が可能。
スクリプトファイル内でもパイプやリダイレクトなど、もちろん使える。

#!/bin/bash

TODAY=`date +'%Y%m%d'`
cat ./result.txt | grep "^$TODAY"
echo "done" `date +'%H:%M:%S'` > ./debug.log

組み込みコマンド

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

echo.sh
#!/bin/sh

echo -e '\u611B'        # 組み込みコマンドのほう
/bin/echo -e '\u611B'
実行結果
$ echo.sh
愛
\u611B

コマンド実行結果の終了コード

シェル上やスクリプト実行時、直前のコマンドの終了コードは特殊変数$?に代入される。終了コードは0(ゼロ)の場合は正常終了を意味し、それ以外はエラーやそれに準ずる状態である8

$ date
Wed Dec 28 14:37:26 JST 2016
$ echo $?
0
$ hogehoge                             # 存在しないコマンド
-bash: hogehoge: command not found
$ echo $?
127                                    # bash がセットしたエラーコード

なお、ifwhileも、0を真値、それ以外を偽値とした真偽判断を行なっている(後述testの項を参照)

バッククォートと$( )

バッククォート9を使って、`command`でコマンドの実行結果を変数に格納できる。これは伝統的な構文で、最近では $(command) も使える。$()は入れ子にできる。

#!/bin/sh

TODAY=`date +'%Y/%m/%d'`
echo $TODAY
NOWTIME=$(date +'%H:%M:%S')
echo $NOWTIME

NORM300=$(expr $(date +%s) / 300 \* 300)
echo $NORM300

外部ファイルからのコマンド実行 source

source は組み込みコマンドで、外部ファイルを読み込み、中に記述されているコマンドを現在のプロセスで実行する。ファイル自体は実行可能ではなくても大丈夫。
.source コマンドの別名として使える。.bash_profile でも使われているので確認してみよう。

~/.bash_profile
# Get the aliases and functions
if [ -f ~/.bashrc ]; then
    . ~/.bashrc
fi

何もしないNOP

NOP とは No OPeration のことで、何もしないという命令。Bashでは :(コロン)でそのようなことができ、これは何もせずに終了コード0(正常終了)を返す。
使う場面は、可読性目的でcase文中においたり、無現ループなど。
また、引数を取れるので、本来のコマンドと一時的に置き換えたりして、デバッグや保守目的(暫定対応)などに使える。

if [ "x$wanna" = "xsleep" ]; then
    : # sleep 60    # commented out for debug.
fi

判断式

test

真偽を判断するのにコマンドtestを用いる。
シェルでは伝統的に、0が正、0以外が偽である。
またifなどと使われる[も実はコマンドで、testと同じ機能を提供する([ 式 ] という形で用いる)。コマンドtestについての説明は、「UNIXの部屋」のtestの解説が分かりやすい
演算子やファイルテスト演算子の説明に関しては、こちらのページでまとめてくれている。

logic.sh
# if 文
if [ "x$1" = "-h" -o "x$1" = "--help" ]; then
  echo "Usage: $0 [-h|--help]"
fi

# while 文
I=0
while [ $I -lt 5 ]; do
  echo $I
  I=`expr $I + 1`
done

cat list.txt | while read LINE; do
  echo $LINE
done

# &&を使った例
[ -s ./target.txt ] && echo "ok, file exists"

文字列判定時のダミー文字列

上のサンプルのようにifで引数を判断する場合に、

if [ "x$1" = "x-h" ]; then

とわざわざ x を入れることが多々ある。なぜか。
例えば、引数が与えられず $1 が空文字列になった場合、 x がないと if [ = "-h" ]; then と解釈されて文法エラーが起こってしまう。これを回避するために適当な文字列を入れるという伝統的な小技である。(もちろん"x"じゃなくてもよい)

case

割と柔軟な分岐を作ることができる。
;; を忘れがちなので注意(; ;)。

while :
do
    echo -n "What'up?) "
    read K
    case $K in
        x)   echo "X-("
                           ;;
        "q") echo "Bye"             # "" で文字列評価してもOK
             break                                     # このbreakはループからの抜け出し
             ;;
        *shirabero)                 # *,? のメタ文字利用がOK
             echo "Nothing is there, Boss"
             ;;
        [yY] | yes | YES )          # 論理和, [abc][a-z][!0-9] もOK
             echo "Yeah!"
                          ;;
        [nN] | no | NO )
             echo "Oops..."
                          ;;
        *)   echo "What?"           # デフォルト. 順番に評価されるので最後に置くとよい.
             ;;
    esac
done

関数

関数はそれを呼ぶ場所より前に定義されていなければならない
呼び出し時は、カッコは不要。

関数とローカル変数

funcname() {
    local FUGA
    set -- $*
    HOGE=$1
    FUGA=$2    # FUGA はローカル変数
}

# Call
funcname
funcname "hoge" "fuga"

return

 返せる値は数値で0~255まで。演算結果返すのではなく、何か自前のステータスコードを返す目的である。

somefunc() {
   [ "x$1" = "x0" ] && return 1
}

(小技)eval を使って関数の返値を変数に代入

他の言語のように、代入式の右辺として使えないのがシェルの関数の短所。つまりは、シェルのは関数ではなくサブルーチンである。
しかし、次のようにevalを使って、擬似的な関数呼び出し+代入が行える。

getdate() {
    eval $1=`date '+%Y/%m/%d'`
}
getdate TODAY
echo $TODAY    # ex) 2015/12/25

プログラムパターン

ファイル入出力

ここに書いた以外の使い方もあるが、最低限 |>>>、あと時々>&1>&2を覚えておけば大抵のことはオケマル。
リダイレクタなどの解説はこちらのQiita記事こちらのページ などを参考にされたし。

パイプ

コマンドの結果を縦棒|を使った パイプ という仕組みを使って次のコマンドに渡せる10。さらに次のコマンドへ…、と連続で渡せる。

pipe_sample
cat logfile.txt | grep '10.0.0.123'
cat logfile.txt | cut -d' ' -f 3 | sort | uniq -n | sort -rn

出力のリダイレクト

コマンドの実行結果(標準出力)を直接ファイルへと書き込むことを、 出力のリダイレクト という。11
1>(上書き) と 1>>(追記) を使う。1 は標準出力を意味するファイル記述子12。ただしここの 1 に限っては省略可能。デフォルトなのだろう。

コマンドのエラー(標準エラー出力)をファイルに書き込む場合には、同様に 2>2>> を使う。1>>>1と合わせて使うことも可能。

それぞれ省略された場合は、画面に出力される。

output_redirect_sample1
ls -la hoge*.txt        # リダイレクトが省略されているので結果とエラーは画面に出力される
df -gh > diskinfo.txt
tail -1 somefile.txt >> logfile.txt    # 追記(アペンド)
ping example.com > reachable.txt 2> unreachable.txt

次の例は、コマンドの実行結果もエラー結果も一つのファイルに出力する例。

output_redirect_sample2
sh somescript.sh >logfile.txt 2>&1    # 2>&1 >logfile.txt ではうまく行かないので注意

明示的に標準エラー出力に出力するのはどうすんだっけ? って、老化による記憶力低下が原因で時々なる。なのでここにメモする。

redirect_to_stderr_sample
echo "STDOUT"        # 標準出力へ
echo "STDOUT" >&1    # 標準出力へ(これはほぼ使われない)
echo "STDERR" >&2    # 標準エラー出力へ

/dev/null

Unix の /dev/null 13 は、即時消去のゴミ箱みたいな使い方もできるし、まっさらなプレーンファイルのような使い方もできる。
出力リダイレクトと組み合わせて使うことが多いので参考に。

dev_null_sample

myscript.sh >/dev/null 2>&1    # 標準出力と標準エラー出力を破棄する (crontable でよく用いられる使い方)
somecommand 2>/dev/null        # エラー出力だけ破棄する
cat /dev/null > myfile.txt     # サイズ0のファイルを作る
cp /dev/null > myfile.log      # (参考)既存のファイルのサイズを0にする(新規であれば上の例に同じ新規作成)

(小技)execをつかって標準出力を一時的にファイルに出力する

> の向きで主客を取り違えてしまうので注意かな。

exec 3>&1 >$tmpfile    # 標準出力(1) を ファイルディスクリプタ3 に複製
cat hogehoge.txt       # 任意のコマンド
exec 1>&3              # ファイルディスクリプタ3 を 1 に複製(標準出力を1に戻す)

入力のリダイレクト

ファイルの内容をコマンドの標準入力へ入力することを、 入力のリダイレクト と言う。
< を用いる。14

input_redirect_sample
mysql -h mydbhost.intranet -u myuser -p mydatabase < sqlfile.sql

ヒアドキュメント

<<EOFといった具合に終端文字列を指定してヒアドキュメント15を作成できる。(文字列EOF部分は任意)
書き方には種類があって、<<EOFと書く場合と、<<'EOF'または<<"EOF"とクォーテーションで囲む囲まないの違いがある。前者はヒアドキュメント部分でも変数展開は行い、後者は行わない。でもダブルクォーテーションなのに変数展開は行わないのはなんか気持ち悪いので、自分はシングルクォーテーションで囲むようにしている。

here.sh
#!/bin/sh

NOW=`date "+%Y/%m/%d %H:%M:%S"`

cat <<EOF
Now is $NOW
EOF

cat <<'EOF'
Now is $NOW
EOF

cat <<"EOF"
Now is $NOW
EOF
実行結果
$ ./here.sh 
Now is 2018/03/14 13:23:11
Now is $NOW
Now is $NOW

タブを削除するヒアドキュメント <<-EOF

<<-EOF のように - を入れることによって、引用行の冒頭のタブを削除することができる。
ただ、削除するのはタブ(\t、ハードタブ)なので、スペースを使ったインデントは削除しない。(なので昨今は使う機会が少ないと思う)

cat <<-EOF
    When you look at the dark side, careful you must be …
    for the dark side looks back.
EOF

ヒアストリング <<<

ヒアストリングはヒアドキュメントの派生で、文字列を渡す場合に使うことができる。

cat <<< "May the force be with you."

組み込みコマンドの活用

set --

setは、--オプションを使う事で、文字列を空白文字で分割して、位置パラメータにセットできる。

set -- $line    # line <= "hoge fuga"
$chunk1=$1      # hoge
$chunk2=$2      # fuga

なお、分割対象の文字列中に、Glob文字(*)があると、カレントディレクトリのファイル名が展開されてしまうので、これを無効にした場合は、-f オプションをつける。

line='* hoge'
set -f -- $line

getopts

getoptsで、コマンド実行時に渡されるオプションパラメータの解析が簡便にできる。

while getopts a:h OPT
do
    case $OPT in
        a) SOMEVAL=$OPTARG           # some command
            ;;
        h) help                      # go help subroutine
            ;;
        *) help
            ;;
    esac
done

shift $(( $OPTIND - 1 ))            # <- 記述を忘れがち!
TARGET=$1                           # $OPTIND-1 分オプション引数が捨てられている

read

コメントよりBuild-inのreadが複数変数を指定できることを教えてもらった。
read はいろいろなオプションも指定できる(The read builtin command)。
サンプル中のシェル変数IFSは、分割子を指定している。

adding.sh
#!/bin/sh

cat somedata.csv | while IFS=',' read p1 p2 p3
do
    if [ "x$p1" = "x1" ]; then
        printf "%d + %d = %d\n" $p2 $p3 `expr $p2 '+' $p3`
    fi
done

## sample: somedata.csv 
#1,1,2
#0,1,1
#1,2,3

行をまるっと使う場合は、read専用シェル変数$REPLYを使う。ただし、readに指定する変数がない場合のみ有効。

musics.sh
cat musiclist.txt | while read
do
    set -- $REPLY
    if [ "x$1" = "x70s" ]; then echo $REPLY; fi
done

## sample: musiclist.txt
#70s Can't Take My Eyes Off You
#70s Dschinghis Khan
#90s Kiss Of Life
#90s Space Cowboy
#70s Moskau

 なお、パイプ経由でwhileへデータを渡すような書き方だと、whileがサブシェルで起動されるため、その外側で定義した変数は更新されない。forや外部ファイルの入力リダイレクト(<)、名前付きパイプなどで行うとよい。(参考元:「bash で,サブシェルが起動される条件」)

db-saiyans.sh
#!/bin/sh

COUNT=0
cat dbdata.txt | while read NAME FLAG VALUE
do
    [ "x$FLAG" = "x1" ] && COUNT=`expr $COUNT + $VALUE`
    echo " - $COUNT"
done
echo "HAGE POWER LEVEL TOTAL: $COUNT"     # 0 

## sample: dbdata.txt 
#Gohan 0 2800
#Piccolo 1 3500
#Krillin 1 1770
db-saiyans2.sh
#!/bin/sh

# 入力リダイレクトを使った方法
COUNT=0
while read NAME FLAG VALUE
do
    [ "x$FLAG" = "x1" ] && COUNT=`expr $COUNT + $VALUE`
done < dbdata.txt
echo "HAGE POWER LEVEL TOTAL: $COUNT"     # 合算されている
db-saiyan3.sh
#!/bin/bash

# 名前付きパイプを使った方法( bash専用 )
COUNT=0
while read NAME VALUE
do
    [ "x$FLAG" = "x1" ] && COUNT=`expr $COUNT + $VALUE`
done < <(cat dbdata.txt)
echo "HAGE POWER LEVEL TOTAL: $COUNT"     # 合算されている

 上のThe read builtin commandにあった、Yes/Noアンサー。便利そうなので引用。

asksure.sh
#!/bin/bash

asksure() {
  echo -n "Are you sure (Y/N)? "
  while read -r -n 1 -s answer; do
    if [[ $answer = [YyNn] ]]; then
      [[ $answer = [Yy] ]] && retval=0
      [[ $answer = [Nn] ]] && retval=1
      break
    fi
  done
  echo # just a final linefeed, optics...
  return $retval
}

### using it
if asksure; then
  echo "Okay, performing rm -rf / then, master...."    # おいw
else
  echo "Pfff..."
fi

[[ ]] はBashの構文で、基本的には [ ] と同じ。ただし、[ ]とは違って&&||など、拡張した表現が使える。こちらの別記事でまとめてみた

trap

シグナルごとに設定可能。といっても使うシグナルは限られているか。KILL(9)はキャッチ不可。

# trap 'command' sign-num 
trap 'echo "see you"' 0           # 0 ... exit
trap 'rm -f $tmpfile;exit 1' 2    # 2 ... INT(Ctrl+C)
trap 0                            # reset (no command, just 1 arg)

misc

シェル変数 $PIPESTATUS

自分はあまり使わないので、詳しく書いているリンクだけ掲載。

コマンドパイプラインの終了コード - 拡張 POSIX シェルスクリプト Advent Calendar 2013 - ダメ出し Blog

スクリプト自身のディレクトリの取得

ここの解説を参考にした。

bash
SCRIPTDIR=$(cd $(dirname $BASH_SOURCE); pwd)

デバッグTips

スクリプトを実行するときに、明示的にshまたはbashコマンドにオプションをつけて実行することで、簡単なデバッグか可能である。

シンタックスチェックをする -n オプション

-n オプションをつけて実行することで、シンタックスチェックをしてくれる。問題なければ何も出ない。
ただ、残念だがそれほど精度は高くない。コマンドのtypoがあったとしても、実行時にしか見つけられないので、通ってしまう。

なお、 -v オプションを共に用いることで、スクリプトのチェック内容が表示される。

$ sh -n test.sh    # if~fi や do~done での閉じ忘れの場合
test.sh: line 10: syntax error: unexpected end of file

実行結果を都度表示してくれる-xオプション

明示的にshまたはbashコマンドに -x オプションをつけて実行することで、コマンドの実行結果をいちいち表示してくれる。コマンドの実行結果や式の様子をトレースできるので、だいぶ楽になるだろう。

$ sh -x db-saiyans2.sh
+ COUNT=0
+ read NAME FLAG VALUE
+ '[' x0 = x1 ']'
+ read NAME FLAG VALUE
+ '[' x1 = x1 ']'
++ expr 0 + 3500
...

なお、 これも -v オプションで、そのコマンド内容が表示されるので、共に用いると良い。

上書きを抑止する -C オプション

大文字のCを使った -Cオプション(または set -C)を使うと、存在しているファイルへのリダイレクト > を使った上書きを禁止できる。>> による追記は可能。また、この上書き禁止状態の場合に使える特殊なリダイレクト >| を使うことで上書きができる。
ファイルが存在しない場合には > をつかっても >> を使っても新しいファイルが作られる。

noclobber.sh
#!/bin/sh

touch hoge fuga
set -C
echo "hoge" > hoge     # エラー. 上書き禁止のため
echo "fuga" >> fuga    # OK.
echo "piyo" >| hoge    # OK. (特殊な >| の使用)

echo "boo" > boo       # OK.
echo "poo" >> poo      # OK.

問題があった場合に実行を止めてくれる-eオプションと-u

-eオプション(または set -e)を使うと、エラーがあったときに処理が止まるようになる。
…のだが、ちょっと一癖ありバグも多いらしい。僕は使っていない。ここを参照されたし)。このToggeter記事も参考に。

-uオプション(または set -u)を使うと、未定義の変数を使うとエラーとなり、その時点で処理が止まる。デバッグやテストでは有用だが、本番では使うべきではないと思う。そうならないように設計しエラーハンドリングを実装し、テストをしたほうが良いから。

コマンドTips

シェルスクリプト構文ではないけど、スクリプト内で使うと便利なコマンド関連Tips。

tar と gzip を使ったパイプ処理による圧縮

gzip-c オプションで、標準入力・出力とで圧縮対象データのやり取りをする。
tar- で、対象を標準入力・出力にすることができる。
もちろん、 bzip2 bunzip2 などで同じことが可能。

$ # making tarball & gzipping
$ tar cvf - target_dir | gzip -9c > target_dir.bkup20160704.tar.gz
$ # ungipping & expand tarball
$ gunzip -c target_dir.bkup20160704.tar.gz | tar xvf -

もちろん、Linuxなどが使っているGNU gzipなら、gzipのオプションで、最初から圧縮されたTarBall .tgz ファイルが作れる。

date コマンドによる日付のパース

date --date="$somestr" '+%Y/%m/%d'
# not only date string but also: today, yesterday, last month, etc.

curl

「カール」と呼ぶのはだいぶ後になって知りました//

curlにパイプ経由でデータを渡す

'-d' オプションで、引数に@-を渡す。

$ cat text.txt | curl -sS -X POST -d @- http://example.com/boopoo

これを利用することで、シェルからJSON情報をPOSTで渡せる。

$ cat <<'EOF'  | curl -sS -X POST -d @- http://example.com/boopoo
{
  "a": 123,
  "b": [
    4,5,6
  ]
}
EOF

まぁ、 joコマンドを導入してもよいかもね。

$ jo a=123 b=$(jo -a 4 5 6) | curl -sS -X POST -d @- http://example.com/boopoo

算術計算の expr と (( ))

コマンド expr を使う方法と、 Bash であれば (( )) 式を使う方法がある。

expr を使う方法は古の時代からある方法で、shしか使えないOSでも使われていた16。計算したい式を引数にして渡す。

expr_fizzbuzz.sh
#!/bin/sh

C=1
while [ $C -le 100 ];
do
    if [ `expr $C % 15` -eq 0 ]; then
        echo "FizzBuzz"
    elif [ `expr $C % 3` -eq 0 ]; then
        echo "Fizz"
    elif [ `expr $C % 5` -eq 0 ]; then
        echo "Buzz"
    else
        echo $C
    fi
    C=`expr $C + 1`
done

Bashの場合であれば、(( ))が使える。(代入する場合の右辺として使う場合は $(( )) とする。また(( )) の中では変数は$で始めなくてもOK。)
こちらの記事にもまとめているのでご参考あれ。

double_parantheses_fizzbuzz.bash
#!/bin/bash

C=1
while (( C<=100 ))
do
    if (( !(C%15) )); then
        echo "FizzBuzz"
    elif (( !(C%3) )); then
        echo "Fizz"
    elif (( !(C%5) )); then
        echo "Buzz"
    else
        echo $C
    fi
    C=$(( C + 1 ))
done

# (( )) をつかうと、for をC言語のように記述できるので、上のwhileは
# このように置き換えることも可能。
# for (( C=1; C<=100; C++ ))
# {
#     ...
# }

自分のグローバルIPアドレスの調べ方

http://ifconfig.io 17 にcurlでアクセスすると、シンプルに自分のグローバルIPを教えてくれる。

example
$ curl ifconfig.io
1xx.1.23.45

とはいえ、個人運営のサービスであると思うので、利用は自己責任で18

改行コードを改行コード文字列に

なんのこっちゃかもだが、改行コードを¥nという文字列にするということ。Perlで。

$ cat some.txt | perl -pe 's/\n/%5Cn/'

文字コード変換

iconv を使うと良い。

# shift_jis で作られたファイル shift_jis.txt を UTF-8 に変換して出力する
$ iconv -f SHIFT_JIS -t UTF-8 shift_jis.txt
$ curl -sS http://example.com/shift_jis_contents | iconv -f SHIFT_JIS -t UTF-8

Macだとあるあるなのが、UTF-8で作られたCSVファイルがExcelで見れない(参考記事)
上の例と逆のことをすれば良い。
ただし、 -c オプションをつけるとよい。そうでないと、変換できない文字に遭遇した場合に処理が止まってしまうので。

$ iconv -c -f UTF-8 -t SHIFT_JIS csv_file.csv > csv_file_shiftjis.csv

エスケープされた日本語文字列のデコード

\uXXXXのようにエスケープされた文字列をさくっとデコードしたい時のワンライナー。とりあえずPerlとPythonを使ったの例。
ただし、サロゲートペア未対応。

# ほげ
echo "\u307b\u3052" | perl -lpe 'binmode(STDOUT,":utf8");s/\\u([0-9a-fA-F]{4})/chr(hex($1))/eg'
# ふが
echo "\u3075\u304c" |  python -c 'import sys; print(sys.stdin.buffer.readline().decode("unicode-escape"))'

(おわり)


  1. 加えてUNIXファイルシステムとパイプと哲学のおかげでもある。 

  2. もともとSolarisから入ったsh派なのでのでBashで何が使えるんだっけ?とか、Bash独自の機能について忘れがちだった経緯がある。 

  3. 自分は「シェバング」と呼ぶ派。Wikipedia では「シバン」で書かれている。pingの読み方と同じように方言化されていてちょっと面白い。なおシェバングについて、これで指し示すコマンドをPATH環境変数に依存させるようにするならば、 envコマンドを使う手もある。 

  4. Ubuntu では dash へのリンクだそうだ。 

  5. 色々な現場でコードを見てきたが、定数・変数の区別なく伝統的に大文字を使っていることが多かった。コマンドがほぼ小文字で書かれるため、可読性目的だったのではないかと推測。最近でこそ他言語のように定数・変数の命名規則について耳にすることもあるが、シェルスクリプトであまりそこをこだわっても綺麗に整理できない場合も多いので、伝統的なルールで良いのではないか、と思う。(そもそも命名規則まで持ち出すほど複雑化するようなスクリプトならば、他の言語の選択を検討せよ。) 

  6. PHPやPerl、Rubyなどはこの文法が参考にされている。 

  7. 組み込み側のマニュアルは、man bashを参照するとよい。 

  8. ただ、コマンドによっては、別なことを意味する場合がある。例えばdiffではExit status is 0 if inputs are the same, 1 if different, 2 if trouble.である。 

  9. 英語圏ではbacktick となることも多い。プログラミング用語として用いる場合はどちらも同じ意味。 

  10. パイプは、パイプラインともいう。UNIXの伝統的な機能である。正確に言えば、コマンドの標準出力の内容を、次のコマンドの標準入力へ渡す仕組みである。名前の由来はもちろん水道管みたいなパイプだろう。 

  11. パイプと違って、標準出力の内容を、コマンドではなく、ファイルやデバイス(例えば画面やプリンタ)へ送るのがリダイレクト。リダイレクションとも。 

  12. ファイルディスクリプターとも。MS-DOSではファイルハンドル。 

  13. /dev/null は、特別なデバイスだ。 

  14. ファイルからの入力リダイレクトは cat somefile | somecommand と同じ結果にはなる。立ち上がるプロセスが少ない分、単純なファイル入力はリダイレクトを使った方が、OSリソース的にはお得かもしれない。 

  15. 英語では here-document。 その機能から hear-document だと思っていた時期が俺にもありました。 

  16. Soralis8 とかは、デフォルトではshしか使えなかったなぁ。 

  17. ifconfig コマンドをオマージュしたURLで面白い。 

  18. たぶん作者はこの人かな。当然ながらプログラムに組み込むなどはしないほうがよい。あくまで確認目的。 

Ping
都内勤務のSEでやんす。ポエ~
yumemi
みんなが知ってるあのサービス、実はゆめみが作ってます。スマホアプリ/Webサービスの企画・UX/UI設計、開発運用。Swift, Kotlin, PHP, Vue.js, React.js, Node.js, AWS等エンジニア・クリエイターの会社です。Twitterで情報配信中https://twitter.com/yumemiinc
http://www.yumemi.co.jp
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away