コマンド

概要

目次

疑問

  • echo '432' | awk '$0~"\."'は何で\が二つ必要なの?
    • シングルクオートとかバッククオートの中で、\が二つ必要なケースが何個かある。統一的な理解は?

問題集

DQ, SQ, 何もなしの違い

  • SQは展開しない、何もなしはスペース区切り始めのものだけ展開、DQは全部展開
A=test
B=$A echo hoge # Bに"test"が入って、hogeが標準出力
B="$A echo hoge" # Bに"test echo hoge"が入る
B='$A echo hoge' # Bに"$A echo hoge"が入る

parallel

time seq 1000 | parallel -j 1 ./a.out | sort -n
3.6s
time seq 1000 | parallel -j 8 ./a.out | sort -n
1.3s
time seq 1000 | parallel ./a.out | sort -n #指定しないとコア数分並列化
1.3s
  • timeはパイプ全体の時間計測する

使用ケース

  • ファイル並列
ls orig/* | parallel -j 10 "echo < {} > {}.log"
  • 行並列
cat big_size.txt | parallel --pipe -L 10000 "awk '{n+=$1}END{print n}'" > compressed
  • グリッドサーチ
lr=(0 0.05 0.10)
units=(100 200 300)
parallel "./a.out {1} {2} > {1}_{2}.log" \
 ::: ${lr[@]} ::: ${units[@]}
  • 入出力ファイルがある場合
seq 10 | parallel bash -c "./game < sample1 > output{}"
# -a - でパイプから入力を受け取る
$ ls * | parallel -a - --dry-run echo
echo a
echo b
# {} で場所を指定できる
$ ls * | parallel -a - --dry-run echo {} end
echo a end
echo b end
# アトミックな処理をしたいなら '' で囲む。というか基本的には囲んだほうがいい
$ ls * | parallel -a - --dry-run 'echo {} 1; echo {} 2'
echo a 1; echo a 2
echo b 1; echo b 2
echo test.xt 1; echo test.xt 2
echo x.txt 1; echo x.txt 2
# {.} で basename が取れる。test.sh なら test
$ echo test.sh | parallel -a - --dry-run echo {.}
sh -c echo test
# zip の例
$ ls *.zip | parallel -a - --dry-run 'mkdir {.}; unzip {} -d {.}; rm {}'

Tips

  • McIloyによるパイプの発明により、部品に分解
    • 部品の組み合わせは指数関数的に存在するので、大きなコマンドを作るより適用性が高い
  • Shell Scriptの文法が汚いのはしょうがない
    • 「手のように」使える必要があるので、文法的に汚いが、じゃあC++みたいに複雑にする?という話がある
  • R.Pike データ構造がアルゴリズムを規定する。"Representation is the essence of programming (Brook)"
  • プログラムのほとんどは例外処理
  • sudo apt-get install manpages-jaを入れると日本語のmanが手に入る
  • treeというコマンドでディレクトリ構造を木構造として描画してくれる。
  • POSIX
    • UNIX互換OSにはさまざまな機能の追加や変更がなされているため、UNIX互換OSの間で最低限必要な仕様や規格を定め、互換性を保つため
    • 全UNIXで動く本、みたいなものを書こうとしたら、これに準拠する必要がある
    • でも拡張を模索し、良いユーザたるには、それは関係ないよね?

計算

VAR_LEFT=5
VAR_RIGHT=5
echo $(($VAR_LEFT + $VAR_RIGHT))
echo $(($VAR_LEFT - $VAR_RIGHT))
echo $(($VAR_LEFT * $VAR_RIGHT))
echo $(($VAR_LEFT / $VAR_RIGHT))
echo $(($VAR_LEFT % $VAR_RIGHT))

デバッグ

  • 下らないミスをなくすために、必ず以下をかくこと
#!/bin/bash
  • コマンド展開
sh -x test_eval_1.sh #展開後実際に起きていることがわかる

bashとshの違い

  • リダイレクト先にメタ記号を使えない

文法

if

if [ $? -eq 1 ] # []の回りにスペース必須
then
   continue
fi

for

  • for文ではcontinue可能

終了ステータス

  • $?で終了ステータス
    • grepで見つかったかどうかなどが分かる

配列

list=("1332" "1333" "1605") # =の間にスペースを入れると死ぬ

include

. list #listは実行可能である必要は無い

find

find .
find . -type f
find . -type ! -name "*~"
find . -type f -a \( -name "*.c" -o -name "*.h" \)
  • なぜかfind . -type ! -name *~は動作しない。
    • hoge*, "hoge*", 'hoge*'の違いが知りたい
    • $"とかもあるの?"

xargs

ls -1 | xargs echo
find . -print0 | xargs -0 echo
find . -print0 | xargs -0 -L1 echo
find . -print0 | xargs -0 -L2 echo
find . -print0 | xargs -0 -I{} echo "<{}>"
  • findからxargsにパイプする場合は、絶対findの-print0をいれてxargsに-0を入れる。
ls | sed -e "p;s/\.jpeg$/\.jpg/" | xargs -n2 mv
for i in *.jpeg ; do mv $i `basename $i .jpeg`.jpg ; done
  • 頑張ってmv, cpなどの二つ同じものが出てくる場合は、このようにsedの;セパレータと-n2で頑張る

    • sifiたんありがとうございます。
  • cat ~/tmp/questions_to_check | xargs -P 100 -n 1 tct questions resolve > ~/tmp/path

    • 並列コマンド実行で便利なやつ

sed

  • オプション-Eで拡張正規表現も使える。
sed -e 's/#//'
  • Hitしなければそのまま表示、Hitすれば処理をして表示
sed -n 's/#//'
  • Hitしてもしなくても非表示
sed -n 's/#//p'
  • Hitすれば処理をして表示
    • そもそもHitしているか、何回Hitしているかを確認するために、-eにpをつけることもある。
echo 'a a' | sed -e 's/ *//'
  • 出力は"a a"
    • なぜなら、1つ目のaの前にマッチするから。
sed -e '1s/rep/p/'
  • 1行目のrepをpに変換

    • 行数の指定は一番初めに書く。指定方法は
      • 1: 1行目
      • 1,10: 1行目から10行目
      • 1,+10: 1行目から11行目
      • ^,$: 行頭から行末まで
    • など
  • sedは{とか}をエスケープ必要がある

    • でもgrepはそうでもない
    • どういうのがエスケープ必要で、必要ないの?
    • Sedは1行でマッチする文字が何通りもある場合は、一番長いものにマッチする。(sed ‘[l],[r]s/<[^<]*>//’の時、前半はオンオフで制御され、後半はきちんと最長一致で制御されている。)

awk

  • awkで置換
echo "replot" | awk '{sub(/^rep/, "p", $0); print $0}'

make

Makefileの$のエスケープは$$

backquote

echo hamu
echo `echo hamu`
echo `echo \`echo hamu\``
echo `echo \`echo \\\`echo hamu \\\`\``

for文

for var in `echo "a b"`
do
    echo ${var}
done
for var in `echo "a b"` ; do echo ${var} ; done
  • 1行の場合は、doの後に;を入れてはならない
  • 1行にする場合は二つ目のechoの後の;が必須。ないとechoがdoneを食ってしまう。

セパレータ

#!/bin/bash IFS=' \t\n'で echo $i done

$''

中の\t, \b, \nなどを展開する。~ これはalt-v [tab], alt-v [backspace], alt-v [enter]に相当する。~

echo "a\na"
\n
echo $'a\na'
a
a

while

while read i;do echo $i;done < file
  • 1行の場合は、doの後に;を入れてはならない

文字列の比較

{var} != "A"

  • if [ ${var} = A ]と何が違うの?違わないとしたら""の意味は何?

何もしない

:

if

  • 中のコマンド実行結果が0ならthenを実行する構文。
if command
then
    expression
fi
if command ; then echo a ; fi
  • 空文字列判定
if [ -z "${opts}" ]; then
    echo "null stream"
else
    echo "not null stream"
fi
  • 空ファイル判定
if [ -s test.txt ]; then
    echo "exist"
else
    echo "not exist"
fi
  • ダブルクオーテーションで囲まないとだめ

test

  • testと[は同じ意味。
if [ condition ]
then
    expression
fi
if  [ -e hoge ] ; then echo a ; fi
  • 一行の時はthenの後に改行を入れてはならない。

grep, egrep

  • egrepはgrepの正規表現が新しいバージョン。+, ?が使える。~
  • 実行結果は、検索に引っかかったら0, 引っかからなかったら1
echo $' \t' |  grep $'^[ \t]+$' ; echo $?
1
echo $' \t' | egrep $'^[ \t]+$' ; echo $?
0
  • ちなみに-Eをつけるとegrepと同じ動作に
    • これはsedでも同じ挙動をする
echo $' \t' | grep -E $'^[ \t]+$' ; echo $?
1

$?とパイプ

echo "hoge" | grep "test" | echo $?
  • 出力をechoが食おうとしているから、grepの終了コード1を正常に取得できない。
echo "test" | grep "test" ; echo $?
  • grepが終わってからechoしているので、1が表示される

Gnuplot

式

plot "file" u 1
plot "file" u ($1)
  • uの後は、カッコでくくると式モードになって、1などは$1などにしなければいけなくなる。

for

  • a
1 2 3
2 3 2
3 5 2
5 8 1
plot for [i=1:3] "a" u i

sprintf

plot for [i=1:3] sprintf("hoge%02d", i) t sprintf("hoge%02d", i)
  • hoge01, hoge02, ...をplot

system

  • num.config
3
num = system("cat num.config")
plot for [i=1:num] sprintf("hoge%02d", i) t sprintf("hoge%02d", i)

数字と文字列の連結

n = system("awk '{print NF; exit}' motenc_state")
replot for [i=2:n] "dv_motor" u i title "Motenc State".(i-1)." [unknown]"

awk

1行目のフィールド数の取得

awk '{print NF; exit}' file

complete

complete

complete -o dirnames -f -X '!*.mp3' mp3nantoka.sh
  • COMPREPLYの中を候補に表示する「だけ」
comptest2 () {
    COMPREPLY=( aaa bbb )
}
complete -F comptest2 hoge
hoge [tab][tab]

comgen

  • aaa, bbbを候補に表示し、補完
comptest1 () {
    COMPREPLY=( `compgen -W "aaa bbb" $2` )
}
complete -F comptest1 hoge
hoge [tab][tab]

定石

function _hoge {
    local arg opts
    COMPREPLY=()
    arg="${COMP_WORDS[COMP_CWORD]}"
    opts="`ls -1`"
    IFS=$'\n'
    COMPREPLY=($(compgen -W "${opts}" -- ${arg}))
    unset IFS
}
complete -F "_hoge" "hoge"

Directoryのパスの場合