Shell Script Advent Calendar 2015 4日目 の投稿です。
以前から自分用にメモしていたものを文字起こししました。
はじめに
仕事でシェルを使い始めて3年くらい経ちました。
途中、python や ruby でスクリプト作ったり、ちょっと zsh に浮気したりしましたが、なんだかんだで今も Bash を使うことが多いです。
この3年間、スーパーシェル芸人(@ebanさん)にご教授頂いたり、Golfしたり(@ebanの影響)、シェル芸勉強会に参加したり(@ebanの影響)してきました。
そんな3年間のまとめとして、シェルスクリプト初めましてだった3年前の私に向けたTips集を書いてみました。
趣旨
各項目ごとに、まず初心者(過去の私がやってた)あるある実装を例示して、その次に、より良さげな実装を例示する構成としています。
実行環境
- OS
- Mac OS X Yosemite
- Mac OS X El Capitan (途中から)
- Shell
- bash 4.3
- brew で bash 入れたので 4.3 になってます。
- 特に手入れしていなければ、お手元の Mac OS X は、3.2 あたりと思いますのでご注意を。
- bash 4.3
他の環境では検証できていません。だいたい動くと思いますが...。
Bashの変数であるある
変数の初期化
シェルと触れ合ったばかりの私は、いつもこんな感じで実装してましたね。
# 引数があればそれを、無ければデフォルト値で初期化
if [ -z "$1" ]; then
hoge="default"
else
hoge=$1
fi
いえ、悪いわけではないです。他の言語と似た形なので、誰にでも分かりやすいと思います。
ただですね、Bashの変数には便利な機能があります。
そんな便利機能の一つ、:-
を使うと、このif文は不要です。
hoge=${1:-default}
一発ですね。え、暗号めいていて初見さんお断りじゃないかって?
でも:-
以外に、:+
や :?
など、本当に便利なんですよ。たくさんあるんです。使いたいんです。
覚えておいて損のない便利さだと思いますよ。
参考) http://qiita.com/bsdhack/items/597eb7daee4a8b3276ba
補足) "
は必要?
@magicant さんから
クォート漏れ、細かいミスの指摘・修正の編集リクエストをいただきました。ありがとうございます!
(チェック不足でした...)
例えばクォート漏れの指摘の一つは、
[ -z $1 ]
→ [ -z "$1" ]
というものでした。
$1
が、"hoge"
とか 123
なら、""
がなくても動きます。
ただ、空白のみや、-r hoge
のような文字列だと厄介なことになります。
[ -z $1 ] && echo '1) $1 is zero length'
[ -z "$1" ] && echo '2) $1 is zero length'
# 空文字
$ bash a.sh ""
1) $1 is zero length
2) $1 is zero length
# 空白のみ
$ bash a.sh " "
1) $1 is zero length
# 空白たくさんでも?
$ bash a.sh " "
1) $1 is zero length
# -から始まると?
$ bash a.sh "-b hoge"
a.sh: 2 行: [: -r: 二項演算子が予期されます
最後の例だと [ -z -r hoge ]
と展開されてから実行されることになります。
"
で囲んでいれば [ -z '-r hoge' ]
と、文字列として扱われるので問題ないのです。
面倒がらず ""
でちゃんと囲まないとですね。
変数に値があった場合のみオプションを追加する
if [ -n "${a_val}" ]; then
a_opt_val="-a ${a_val}"
fi
something_cli ${a_opt_val} sub_cmd
${a_val}
に何か文字が入っていたら、a_opt_val="-a ${a_val}"
という変数を定義してるんですね。とても分かりやすいです。
でも、:+
を使えばこのif文は不要です。
a_opt_val="hoge"
something_cli ${a_opt_val:+-a "${a_opt_val}"} sub_cmd
# この時、${a_opt_val:+-a "${a_opt_val}"}は -a hoge となる。
a_opt_val="-a hoge"
ではなく、a_opt_val="hoge"
にしている理由ですか?
変数 a_opt_val の値を気軽に使い回せるからです。
変数の未定義とTypoに気付きたい
変数とTypoは人類永遠の課題ではないでしょうか。
Typoをしないプログラマは、神の手(とキーボード)を持っているか、あるいはTypoしないようプログラムされた存在(ロボット)以外に存在しないのではと思います
。
そのくせ、Typoは恐怖の処理を招きます。
tmp_dir="myTmp"
rm -rf ${tmo_dir}/* # => Don't pleeeeeeeease!!
そんなのエディターやIDEに任せるべき?
それも一つの道でしょう。私達が真に戦うべき相手はTypoなんかじゃないはずです。
とはいっても、Typoじゃなくても、実装ミスや勘違いで変数の未定義・空文字となってしまうことはありますよね。
変数の未定義チェック
if [ -z "${forgetful_var}" ]; then
echo "forgetful_var is not defined!!"
exit 1
fi
やっぱり if [ -z ${var} ]
の構成は分かりやすいですからね。
でも、変数が出てくる度に全部 if
でチェックするつもりですか?
function check_defined() {
if [ -z "${1}" ]; then
echo "Not defined!!"
exit 1
fi
}
check_defined ${forgetful_var_1}
check_defined ${fargetful_var_2}
check_defined ${forgetful_var_3}
なるほど、関数化すればif文は1つになりますね。
ただ、これって、どの変数が未定義だったのか分かりにくいですよね。
だってどの変数が未定義でも "Not defined!!"
って出力されるんですから。
ともあれ、おもむろに set -u
しましょうか。
set -u
forgetful_var_1="hoge"
forgetful_var_2="hoge"
forgetful_var_3="hoge"
echo ${forgetful_var_1}
echo ${fargetful_var_2}
echo ${forgetful_var_3}
hoge
a.sh: 行 7: fargetful_var_2: 未割り当ての変数です
単純明快ですね。
参考) http://qiita.com/m-yamashita/items/889c116b92dc0bf4ea7d#-u
え、例えば変数 forgettable
は、未定義でも問題ないようにしたい?
一旦 set +u
するか、${forgettable:+${forgettable}}
なら、forgettable
が未定義でもエラーになりません。
set -u
str="hoge"
echo ${str}
set +u
echo ${forgettable}
set -u
set -u
str="hoge"
echo ${str}
echo ${forgettable:+$forgettable}
こんな感じです。
おっと、未定義だけでなく空文字も許したくない、ですか?
set -u
は未定義しか検知しませんからね。
そんな時は :?
を使うとお手軽です。
should_be_defined=
: ${should_be_defined:?} #=> 空文字も許さない
a.sh: 行 2: should_be_defined: パラメータが null または設定されていません
:
コマンドという何もしないコマンドと組み合わせれば、もはやこれ以上ないシンプルさですね。
補足) shellcheck
シェルスクリプトにおける、いわゆるLintのようなツール shellcheck
というものがあります。
brew install shellcheck
でさくっとインストールできます。
これを使うと、未定義だけでなく、未使用な変数の検出をしてくれます。
$ cat a.sh
#!/bin/bash
echo "${no_defined_var}"
no_use="var"
$ shellcheck a.sh
In a.sh line 2:
echo "${no_defined_var}"
^-- SC2154: no_defined_var is referenced but not assigned.
In a.sh line 3:
no_use="var"
^-- SC2034: no_use appears unused. Verify it or export it.
他にも色々検出してくれますが、あまり詳しくないので紹介のみに留めます。
2015/12/06 追記
@b4b4r07 さんから
typoの指摘・修正いただきました。ありがとうございます。
(shellcheckのtypoがちらほら...)
変数の変数
変数に慣れてくると、こんな事したくなりますよね。
mark_base="@(^ ^)@"
markA=mark_base #=> markA="mark_base" と同じ意味です
echo ${markA}
#=> @(^ ^)@ と出力したい(実際は mark_base と出力される)
そういえば、bashの変数って ${parameter}
でしか値を取得できないんでしたね。
でも他の言語なら、変数を変数に代入するのって普通のことじゃないですか。
mark_base = "@(^ ^)@"
markA = mark_base
p markA
#=> "@(^ ^)@"
ググったら eval
で頑張る方法が出てきましたか?
ああー、なるほど確かにできますね。
mark_base="@(^ ^)@"
markA=mark_base
echo `eval echo '$'$markA`
#=> "@(^ ^)@"
なんとか出来ました。でも、あまり気持ちのいい感じしませんね。それに eval
は難しいです。
いっそ declare -A
で連想配列を作った方が分かりやすいでしょう。
declare -A marks
marks=(
["mark_base"]="@(^ ^)@"
)
markA=mark_base
echo ${marks[${markA}]}
#=> "@(^ ^)@"
シンプルさからは遠く、ちょっと大げさな感じですね。
しかし、やっぱり変数の変数って難しいのでしょうか。
と思ったら、${!var}
というのがあるんですよ。
mark_base="@(^ ^)@"
markA=mark_base
echo ${!markA}
#=> "@(^ ^)@"
ちなみに man には次の説明しか書いてないです。
${parameter}
(中略)
If the first character of parameter is an exclamation point (!), it introduces a level of variable indirection.
${!parameter}
と分かりやすく区切られていないんです。これは気付きませんね。
man ですら探し辛い書き方なので、使う時はコメントアウトを添えておくと(数カ月後の自分に)親切かもしれませんね。
grep あるある
特定のプロセスを確認
コンソールで試すがままなgrep
% ps aux | grep jenkins | grep -v grep | grep java
yasuhiroki 24501 0.0 3.9 6421828 654108 ?? S 11:22PM 1:16.61 /usr/bin/java -Dmail.smtp.starttls.enable=true -jar /usr/local/opt/jenkins/libexec/jenkins.war
- jenkinsでgrepしたら
- grep自身のプロセスが出てきたので-vで取り除き
- jenkinsのつくプロセスが他にもあったから
- とりあえず java でもう一回 grep
そんな感じで気付いたらパイプの連続です。
いえ気持ちは分かります。試しながらパイプで繋いでいれば、こうなりますよね。
でも、さすがに余計な処理が多すぎますね。
まず、grep自身のプロセスの取り除くには、ちょっと正規表現を使うだけで達成できます。
↓は良く見かける例です。
ps aux | grep "ps au[x]"
# grep "ps aux" も検出しちゃう
$ ps aux | grep "ps aux"
yasuhiroki 5948 0.0 0.0 2457428 460 s000 U+ 11:49PM 0:00.00 grep ps aux
root 5947 0.0 0.0 2437344 1008 s000 R+ 11:49PM 0:00.00 ps aux
# []付けるだけで取り除ける
$ ps aux | grep "ps au[x]"
root 5949 0.0 0.0 2436320 1000 s000 R+ 11:50PM 0:00.01 ps aux
grep "ps au[x]"
という文字列は、正規表現 "ps au[x]"
ではマッチしないですからね。
さて、今回欲しいのは、javaコマンドで jenkins.warを引数にしているプロセスなのでしたね。
では、そのように書きましょう。
% ps aux | grep "java.*jenkins\.war"
yasuhiroki 24501 0.0 3.9 6421828 654112 ?? S 11:22PM 1:16.72 /usr/bin/java -Dmail.smtp.starttls.enable=true -jar /usr/local/opt/jenkins/libexec/jenkins.war
java の grep
も、grep -v
も不要になりました。
jenkins\.war
が地味にミソです。jenkins.war
としてしまうと、grep
自身のプロセスも引っかかってしまいます。
考えるのが面倒なら、java.*jenkins\.wa[r]
と、とりあえず最後の文字を []
で囲んでしまいましょうか。
そういえば、どうして jenkins のプロセスを探していたのです?
特定のコマンドのプロセス番号が知りたい
% ps aux | grep "java.*jenkins\.war" | awk '{print $2}'
24501
pgrep を使いましょう。
% pgrep -f "java.*jenkins\.war"
24501
ああ、シンプル。
特定のコマンドの引数を知りたい
これはまあ、grep
で良いような気がしますね。
ちょっと正規表現修正して -o
すれば済みます。
# コマンド名 java を残して良いなら
$ ps aux | grep -o "java.*jenkins\.war.*"
java -Dmail.smtp.starttls.enable=true -jar -Xms512m -Xmx2048m -XX:MaxPermSize=128m -Dfile.encoding=utf-8 /usr/local/opt/jenkins/libexec/jenkins.war --httpPort=8080
# コマンド名 java を残したくないなら(引数だけにしたいなら)
$ ps aux | grep -o "java.*jenkins\.war.*" | cut -d' ' -f 2-
-Dmail.smtp.starttls.enable=true -jar -Xms512m -Xmx2048m -XX:MaxPermSize=128m -Dfile.encoding=utf-8 /usr/local/opt/jenkins/libexec/jenkins.war --httpPort=8080
一応、代替案も考えてみましたが、これだ! というものがありませんでした。
# Linuxなら取れるんだけど、OSXでは取れなくて...
cat /proc/$(pgrep -f 'java.*jenkins')/cmdline
2015/12/13 追記
@heliac2000 さんから、pgrep -l
を使う手もあると教えていただきました。
ls -l
と同じような意味(Long Output, Long Format)なので覚えやすいですね。
$ pgrep -fl 'java.*jenkins' | cut -d' ' -f3-
-Dmail.smtp.starttls.enable=true -jar -Xms512m -Xmx2048m -XX:MaxPermSize=128m -Dfile.encoding=utf-8 /usr/local/opt/jenkins/libexec/jenkins.war --httpPort=8080
特定のファイル名を探す
# カレントディレクトリを捜索
ls -l | grep 999
# もっと深い階層にあったかもと思って再帰的に検索
ls -lR | grep 999
ls
と grep
の組み合わせ。よくやってました。
確かに grep
は何にでも応用できますからね。最強です。
しかして、ls -lR
だと、どの階層のファイルか分かり辛くないですか?
$ ls -lR | grep 999
-rw-r--r-- 1 yasuhiroki staff 4 12 2 02:20 1999.txt
-rw-r--r-- 1 yasuhiroki staff 4 12 2 02:20 2999.txt
-rw-r--r-- 1 yasuhiroki staff 4 12 2 02:20 3999.txt
-rw-r--r-- 1 yasuhiroki staff 7 12 2 02:20 4999.txt
ファイルの検索は find
の仕事ですよ。
find . -name "*999*"
$ find . -name "*999*"
./tmp_dir/1999.txt
./tmp_dir/2999.txt
./tmp_dir/3999.txt
./tmp_dir/4999.txt
これでファイルの一覧がパス付きで取れました。
お目当てのファイルを探し出しましたけど、何か、続けてやりたいことがあったのでは?
特定の名前を持つファイルサイズ取得
$ find . -name "*999*" -ls
2368802 8 -rw-r--r-- 1 yasuhiroki staff 4 12 2 02:20 ./tmp_dir/1999.txt
2369802 8 -rw-r--r-- 1 yasuhiroki staff 4 12 2 02:20 ./tmp_dir/2999.txt
2370802 8 -rw-r--r-- 1 yasuhiroki staff 4 12 2 02:20 ./tmp_dir/3999.txt
2371802 8 -rw-r--r-- 1 yasuhiroki staff 7 12 2 02:20 ./tmp_dir/4999.txt
4,7 のある行がサイズです。
ううん...。分かりにくいですね。
$ find . -name "*999*" -exec du -h {} \;
4.0K ./tmp_dir/1999.txt
4.0K ./tmp_dir/2999.txt
4.0K ./tmp_dir/3999.txt
4.0K ./tmp_dir/4999.txt
du
コマンドで知りたい情報だけを得るようにしてみました。
ちょっとコマンドが長くなってしまいましたが。単位付きで分かりやすいですね。
でも気を付けてください。du
コマンドのサイズって、ブロック単位なんですよ。
良く考えてくださいね。
知りたいのはファイルが消費する容量のサイズ?
それともファイルに含まれるバイト数?
バイト数なら wc
を使えば良いですね。
$ find . -name "*999*" -exec wc -c {} \;
4 ./tmp_dir/1999.txt
4 ./tmp_dir/2999.txt
4 ./tmp_dir/3999.txt
7 ./tmp_dir/4999.txt
補足 find -exec +
の方が適切なシーン
@fumiyasさんから、find -exec +
を教えていただきました。
知らなかったので慌てて man を読んだところ、可能な限り引数にまとめて渡してコマンドを実行する、とのことです。
つまり今回の例だと、
find . -name "*999*" -exec wc -c {} +
とすれば、
wc -c ./tmp_dir/1999.txt ./tmp_dir/2999.txt ./tmp_dir/3999.txt ./tmp_dir/4999.txt
を実行することになり、起動するコマンドが1つになります。ようするに xargs
と似たような感じですね。(man
にも、xargs
と似たような振る舞いをすると書いてありました)
また、このケースだと、 wc
を使うより stat
コマンド、もしくは find -printf
を使うほうがより効率的とのことです。
# BSD版 stat
$ find . -name '*999*' -exec stat -f '%z %N' {} +
4 ./tmp_dir/1999.txt
4 ./tmp_dir/2999.txt
4 ./tmp_dir/3999.txt
7 ./tmp_dir/4999.txt
# GNU版 stat
$ find . -name '*999*' -exec gstat -c '%s %n' {} +
4 ./tmp_dir/1999.txt
4 ./tmp_dir/2999.txt
4 ./tmp_dir/3999.txt
7 ./tmp_dir/4999.txt
特定の名前を持つファイルのパーミッションを変更したい
項目を分けましたが、実装方法はサイズを調べる時と変わりないですね。
find . -name "*999*" -exec chmod 755 {} \;
ファイル内に hogehoge が含まれていたら条件分岐したい
if [ "`grep 'hogehoge' hoge.txt`" ]; then
echo "found!"
fi
grep
して検索文字が含まれる行があれば、確かに条件式は真になりますね。
grep
で抽出した行が複数あっても、""
で囲んでいるから大丈夫ですね。
でも、grepしたい検索文字が空白だったらどうしましょう。
検索したい文字が '
とか "
とか含まれていたら?
いえ、実はそのまま空白や"
を検索文字列に書けば動きます。
でも気になりますよね。あれっ大丈夫なんだっけ、って。
それに '
を検索したい時は流石に、考えないといけませんね。
エスケープの参考)
http://qiita.com/cocodrips/items/bb3640a9834c8978d48a
なるべく、クォートは減らすに越したことはないですよ。
grep 'hogehoge' hoge.txt
if [ $? = 0 ]; then
echo "found!"
fi
終了コードを使ったのですね。
これなら、grep
は grep
の記述のみに集中すれば良いので、余計な心配しなくて良いですね。クォートも必要最小限で済んでます(実は '
も不要です)。
でもこれって、[ $? = 0 ]
をあちこちに書く未来が見えますね。
大したことないですって? [ $? = 0]
としてしまって、syntaxエラーになったことありませんか?
ところで if
の後は []
が続くなんて決まりありましたっけ?
if grep 'hogehoge' hoge.txt; then
echo "found!"
fi
if
が条件分岐に使用するのは終了コードなのです。だから、素直にコマンドを書いてしまえば良いんです。
この記述なら、
if
grepコマンドの終了コードが0なら
echo "found!"
する
と、読んだ通りの条件分岐になりますね。
grepの出力が邪魔になる? /dev/null
に突っこんでもいいですけど、-s
と-q
オプションを使った方が良いですよ。エラー出力も隠せますからね。
ついでに、この if
ってそもそも if
にする必要あるんでしょうか?
grep -sq 'hogehoge' hoge.txt && echo "found!"
&&
は 左辺のコマンドの終了コードが 0 だった時に右辺を評価します。
if
も []
も消えて、 本当に処理したいコマンド grep
と echo
だけに集中できますね。
hogehoge
が無かった時の処理ですか?
grep -sqv 'hogehoge' hoge.txt && echo "not found!"
grep -sq 'hogehoge' hoge.txt || echo "not found!"
grep -v
か ||
で良いですね。
もし ||
を使う場合は、 ファイル hoge.txt
が存在しない時も echo "not found!"
が実行されることに気を付けてくださいね。
どちらを使うべきかは、実装したい処理によりますから。
あー、やっぱり三項演算したいですか。ちょっと工夫が必要なんです。
grep -sq 'hogehoge' hoge.txt && echo "found!" || echo "not found!"
って、if
文で表すと↓になるんですよ。。
if grep -sq 'hogehoge' hoge.txt; then
if ! echo "found!"; then
echo "not found!"
fi
else
echo "not found!"
fi
echo "found!"
が失敗した時も(Typoもあり得ますね)、echo "not found!"
が出力されてしまうんです。
変数の中に特定の文字があるか調べたい
having_hoge_var="@(^ ^)@ hoge!"
echo ${having_hoge_var} | grep -sq "hoge" && echo "OK!"
grep で調べるために、echo で変数の値を標準出力にはきだしているんですね。
でも、echo ${having_hoge_var} | grep -sq "hoge"
って長くないですか?
[[ ]]
という [ ]
の拡張を使うと、すっきりします。
having_hoge_var="@(^ ^)@ hoge!"
[[ ${having_hoge_var} =~ "hoge" ]] && echo "OK!"
[[ ${having_hoge_var} == *hoge* ]] && echo "OK"
どちらでも "OK" です。
整数の計算あるある
expr コマンドを使ってしまう
i=`expr $i + 1`
「シェルスクリプト 計算」でググると出てきますものね。あー、bc
コマンドも出てきますね。
なんだか、Bashってコマンド使わないと四則計算できないなんて不便ですね。
でも本当に不便なのは、もっと便利な方法があるのにゆるい検索では知ることができないWebの世界だと思いますよ。
ループでインクリメントしたい
i=0
while [ $i -lt 10 ]
do
i=`expr $i + 1`
echo $i
done
1~10を、インクリメントしながら表示しようとしたらこんな感じですか。
何度見ても、expr $i + 1
って何だか格好悪いですよね。
(())
を使えばすっきりです。
インクリメントしたいなら、見た目もパフォーマンスも圧倒的にこちらが良いです。
((i++))
Rubyですら使えない i++
ができるんです。
i=0
while [ $((i++)) -lt 10 ]
do
echo $i
done
(())
だけだと、何も出力されずただ実行されるのみです。
$(())
とすれば、計算結果の数値を得られるので、そのまま比較に使えるわけです。
やりたいのって、こういう事でしたよね?
でも、もうちょっと見覚えのある書式にしたいですよね。
それに (())
は、足し算しかできないわけじゃないんですよ?
i=0
while (( i++ < 10 ))
do
echo $i
done
[ ]
の中で使うとリダイレクトになってしまう比較演算子 <
も、(())
なら使えます。
for
では、もっと見覚えのある形にできますよ。
for ((i=1; i <= 10; i++))
do
echo $i
done
さらに for
であれば do ~ done
すら使わずに済むんです。
max_cnt=10
for ((i=1; i <= max_cnt; i++))
{
echo $i
}
見慣れた書式で安心できますね。
ちなみに 1~10を一行ごとに出力するなら、forを使う以外にも echo
と tr
や grep
で(シェル芸の範疇になりそうなので省略)
while read あるある
ファイルから一行ずつ読み込んで、あれやこれやってしますよね?
そんな時に使うといったら、while read
でしたね。
csvを読み込む
cat data.csv | while read line
do
_1=`echo ${line} | cut -d',' -f1`
_2=`echo ${line} | cut -d',' -f2`
_3=`echo ${line} | cut -d',' -f3`
echo "Column 1: $_1"
echo "Column 2: $_2"
echo "Column 3: $_3"
done
cut
で ,
区切りにして、1つずつフィールドを読み取って...
大変ですね。列が20とか100とかなっても、このまま頑張りますか?
頑張りたくないですよね。
ようするに,
でsplitすれば良いんですよね?
cat data.csv | while IFS=',' read _1 _2 _3
do
echo "Column 1: $_1"
echo "Column 2: $_2"
echo "Column 3: $_3"
done
デフォルトの区切り文字は空白文字なので、',' では区切ってくれません。
なので↑のようにすれば、read
してる時だけ、区切り文字を ,
した上、各フィールドの値を 各変数に代入できます。
色々と省略できてますね。
IFSを変更する時は、元の値を保持しておいて(IFS_ORI=$IFS
とかして)、あとでIFSを元に戻さないと大変な目にあいますけど、この書き方なら不要です。
ただ、列数が増れば増えるほど辛くなることに変わりはないですね。
配列にすればもう少し扱いやすいでしょう。
cat data.csv | while read line
do
cols=(`echo $line | tr ',' ' '`)
for ((i=0; i < ${#cols[@]}; i++)) {
echo "Column $((i+1)): ${cols[$i]}"
}
done
read -a
で配列化はもっとシンプルに
2015/12/07 追記
@eban さんのブログで、read -a
を使えばもっとシンプルになると指摘されました。
cat data.csv | while IFS=, read -a cols
do
for ((i=0; i < ${#cols[@]}; i++)) {
echo "Column $((i+1)): ${cols[$i]}"
}
done
,
区切りの配列にできるので、空白が入っていても安心ですね。
えっ、こんな csv だったらどうするか?
2015/12/04 00:00:00,"A,B",c,d\ne
Column 1: 2015/12/04
Column 2: 00:00:00
Column 3: "A
Column 4: B"
Column 5: c
Column 6: dne
ぐっ、こ、これは。。。
$ cat data.csv | ruby -r 'csv' -e 'CSV.parse($<).each{|row| row.each_with_index{|c,i| puts "Column #{i}: #{c}"}}'
Column 0: 2015/12/04 00:00:00
Column 1: A,B
Column 2: c
Column 3: d\ne
つまり、CSVのパースを真剣にやるなら、シェルスクリプトは止めたほうが良いという話でした。
最後に
Greg's Wiki を読みながら書いてみるのが一番の勉強だと思います。
参考
-
http://mywiki.wooledge.org/EnglishFrontPage
- Greg's Wiki
- 実装方法に悩んだ時は、ここのBashFAQを見れば大抵模範解が得られます
- 良くやりがちなミスをまとめたページは、最近日本語訳がでましたね
-
https://github.com/jlevy/the-art-of-command-line
- シェルというよりは、コマンドラインで作業する時のノウハウ集です
- 日本語訳もあります
-
https://github.com/alebcay/awesome-shell
- すばらしいシェルスクリプトたち
-
http://explainshell.com/
- ワンライナーなシェルスクリプトを解析、解説してくれる素敵サービスです
- 隣のシェル芸人の作品を紐解くときにでも
-
http://www.shellcheck.net/
- いわゆるLintのようなものです
- brewでインストールしてコマンドからも使えます
- Qiita記事
- 検索するといい記事たくさんあります
明日は @yasuto777 さんです!