Edited at

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

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

旧来バッチ処理で本領を発揮するシェルスクリプトだったが、昨今はDocker(Dockerfileや起動スクリプトなど)で活用される機会も多いのではなかろうか。

しかしながら、他の言語ではみられない記法や文法のルールがあったり、その一方で似通っている書き方もあったりと、いろいろ混乱しがちなものこのBASHの特徴である(と私は思っている)。

いちいちGoogle先生に確認しながら書くことも多かったりしたので、自分のメモがてら Bashのポイントをまとめた2

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


記法・特殊変数


基本


  • そんな高級な言語仕様じゃないし、わりとデバッグやテストもしにくい。


    • でも、他のプログラム言語だとコードを何行も書かなければならない処理も、コマンド数発とパイプのワンライナーでいけたりとか、その爽快感は魅力。



  • 他のプログラムにあるような、命令後に;(セミコロン)はいらない。ただし、1行に複数命令を各場合は、明示的に;で区切ることが可能。

  • コメントは#を用いる。#以降が無視される。


  • if ~ fi とか case ~ esac とか、他の言語じゃ見られないような、洒落た文法。

  • 括弧はいろいろな文法を表現できるようになっている。別記事にまとめてみたので、ご参照あれ。 - Bashの括弧のノウハウ(まとめ)


シェバング

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

#!/bin/sh

#!/bin/bash

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

Linuxではshbashへのリンクとなっているが、シェバングが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の構文として解釈される。


変数・文字列


変数と代入

任意の文字列を使った変数が作れる4。参照時には$をつけるが、代入する際は$を使わない。代入は=を用いる。

右辺式が文字列の時で、(途中に空白などがなく)式として成り立っていればクォーテーションしなくても代入が可能。

=の前後に可読性目的で空白を入れてはいけない。言語的にエラーである。

他のスクリプト言語のように${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


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

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

$ NUM=42

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


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

シングルクォーテーションマークで囲んだ文字列では、式は展開されない5

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

$ echo 'THE ANSWER IS $NUM'          # THE ANSWER IS $NUM (展開されない)

$ echo 'Hello!!' # ""だと、 !! が直前のコマンドに置き換わる


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

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

$ BOO='TSUNODA "''STAR''" HIRO'      # 'TSUNODA "' + 'STAR' + '" HIRO'

$ echo $BOO # TSUNODA "STAR" HIRO
$ POO="NISHIKINO '""STAR""' AKIRA" # "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

echo $HOGE            # (null)

echo ${HOGE:-hoge} # hoge ... just behalf
echo $HOGE # (null)

echo $FUGA # (null)
echo ${FUGA:=fuga} # fuga
echo $FUGA # fuga ... also substituting value as default



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


コマンド実行

任意のコマンドは、スクリプトファイルで記述して実行したり、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)と、シェルの組み込みとして存在しているコマンドがあったりする。

シェルスクリプト上ではパスを指定しないと組み込みのものが使われる。機能に差異があったりするので、前者を使う場合は明示的にパスを指定するなどして区別する。6


echo.sh

#!/bin/sh

echo -e '\u611B' # 組み込みのほう
/bin/echo -e '\u611B'



実行結果

$ echo.sh


\u611B


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

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

$ date

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

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


バッククォートと$( )

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

#!/bin/sh

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

EXAMPLE=$(expr $(date +%M) + $(date +%S))
echo $EXAMPLE


外部ファイルからのコマンド実行 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を覚えておけば大抵のことはオケマル。


パイプライン

コマンドの結果を パイプ という仕組みを使って次のコマンドに渡す。多重で渡せる。

cat logfile.txt | grep '10.0.0.123'

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


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

コマンドの結果(標準出力)をファイルに記録したいときは、1>(上書き) と 1>>(追記) を使う。ただし1は省略可能。

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

このように、出力をファイルへ書き込むことを リダイレクト という。

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

ls -la hoge*.txt        # リダイレクトが省略されているので結果とエラーは画面に出力される

df -gh > diskinfo.txt
ping example.com > reachable.txt 2> unreachable.txt

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

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

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

なのでここに記録する。

echo "STDOUT"        # 標準出力へ

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

リダイレクタなどの解説はこちらのQiita記事こちらのページ などを参考にされたし。


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

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

exec 3>&1 >$tmpfile    # 標準出力(1) を ファイルディスクリプタ3 に複製

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


ヒアドキュメント

<<EOFといった具合に終端文字列を指定してヒアドキュメント9を作成できる。(文字列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 オプションで、そのコマンド内容が表示されるので、共に用いると良い。


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

-eオプション(または set -e)を使うと、エラーがあったときに処理が止まるようになる。

…のだが、ちょっと一癖ありバグも多いらしい。僕は使っていない。ここを参照されたし)。このToggeter記事も参考に。

-uオプション(または set -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


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

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


example

$ curl ifconfig.io

1xx.1.23.45

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


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

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



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



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



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



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



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



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



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