綾小路龍之介の素人思考

Tools > bashのメモ

いろいろ使えるなぁと思ったコマンドラインの技をメモしていこう。何番煎じだろうか。自分のためのメモと色々キーバインドがかぶったりするかどうかのチェックのためのメモにしよう。

目次

[date] W3CDTFフォーマットでdateコマンド

W3CDTFで日付を見たい場合もあるのよ。

$ date "+%Y-%m-%dT%T+09:00"
2009-04-07T16:38:08+09:00
$ echo '#!'`which bash` > ~/bin/w3cdtf
$ echo date '+%Y-%m-%dT%T+09:00' >> ~/bin/w3cdtf
$ chmod 0755 ~/bin/w3cdtf
$ echo "PATH="\$PATH":\$HOME/bin" >> ~/.bashrc
$ source ~/.bashrc
$ w3cdtf
2009-04-07T16:39:49+09:00

~/.bashrcにaliasかfunctionで登録すると、vimのシェルコマンドのリダイレクトがコマンドが見つからないというエラーを出して終了する。

cronのジョブをランダムに走らせる

一時間に1回コマンドやシェルスクリプトを走らせたいけれど、走るタイミングはランダムであってもらいたい。呼び出しのタイミングが一定であればcronだけでOKだが、ランダムなタイミングだとそういうわけには行かない。そんな場合にどうするのか。1時間に1回という点はcronで、ランダムなタイミングはsleepで解決してみる。呼び出されるコマンドやシェルスクリプトの一般性をなくさないために呼び出すタイミングをランダムにするためのシェルスクリプトを介してコマンドやシェルスクリプトを呼び出す。

$ cat randomsleep.sh
#!/bin/bash
NUM=`expr $RANDOM % 3600`
echo $NUM
sleep $NUM
$ sh randomsleep.sh; wget http://www.google.com -O -

randomsheep.shで0-3599秒の適当な時間だけsleepする。其の後に適当なプログラムを呼び出す。このくらいならばシェルスクリプトにしなくても良いのかもしれない。

$ sleep `expr $RANDOM % 3600`; wget http://www.google.com -O -

ということで、これをcrontabに登録すれば「1時間に必ず1回だけ呼び出されるけれど、呼び出されるタイミングは呼び出されるたびに違う」という挙動を実現できる。登録のときは%をエスケープする(\%にする)こと。

$ crontab -e
0 * * * * sleep `expr $RANDOM \% 3600`; wget http://www.google.com -O -
  1. jijixi's diary - 書くネタが無い , wget で結果を標準出力に吐く
  2. シェルスクリプトでランダムな数字を得る方法 - World Wide Walker
  3. cron 登録ジョブ crontab のバックアップ
  4. crontabでは%をエスケープしなければならないなんて・・・ - はてな?のぐうたら玉子丼
  5. crontab - Wikipedia

この方法の問題はランダムな時間だけ、ジョブを呼び出したシェルとsleepが常駐してしまうことだ。これによってメモリが消費されてしまうので、ぎりぎりのメモリで頑張っているマシンでは辛い方法だ。例えば、psでジョブのプロセスIDをチェックして、topのバッチモード一回でメモリ使用量を確認すると下のようになる。

$ ps waxf
$ top -s -p 16929 -p 16932 -b -n1
top - 17:27:24 up 2 days, 20:51,  1 user,  load average: 0.29, 0.22, 0.19
Tasks:   2 total,   0 running,   2 sleeping,   0 stopped,   0 zombie
Cpu(s): 16.8%us, 22.2%sy,  0.1%ni, 50.6%id,  8.4%wa,  0.4%hi,  1.5%si,  0.0%st
Mem:    125988k total,   123352k used,     2636k free,      768k buffers
Swap:   369452k total,    57296k used,   312156k free,    38544k cached
  PID USER      PR  NI  VIRT  RES  SHR S %CPU %MEM    TIME+  COMMAND
16929 hoge      18   0  4128 1420 1000 S  0.0  1.1   0:00.01 sh
16932 hoge      19   0  2744  468  408 S  0.0  0.4   0:00.00 sleep

topのVIRTで確認するとうちのコンピュータでは、ジョブが終了するまでの間、呼び出しシェルが4.1MBytes、sleepが2.7MBytesほど仮想メモリを占有してsleep状態になってしまう。1時間に一回という微妙な粒度でwgetを呼び出しているので、最長で1時間、最短で0秒間、平均で30分間、つまりこのジョブはメモリ消費の観点からすれば「6.8MBytesの仮想メモリを占有するプロセスが1日の内12時間走る」ことと同義である。これは無駄といえば無駄だな。

  1. @IT:httpd.confによるWebサーバの最適化(3/3)
  2. VIRT top - Google 検索
  3. topコマンドのバッチモード - カサヒラボ
  4. top バッチモード - Google 検索

さて、上のジョブを実現させたcronジョブにおける、一番の無駄な点はプロセスがメモリを占有した状態でsleep状態になってしまい、其の平均が30分間である点だ。crontabを自動的に書き換えるスクリプトを書いて、この無駄を排してみよう。

cronジョブは最短で1分の粒度で走らせることが出来る。そのため、1日1回当日24時間分のジョブをcrontabに追加出来れば、sleep状態になっている時間の平均を30秒に減らすことが出来、1日平均で24回*30秒=12分にsleep時間を減らせる。そこで、1日に1回0時0分にcrontabを書き換え、1時間に1回目的のジョブをこなすエントリーを追加し、過去に行ったエントリーを削除するシェルスクリプト(pushjob.sh)を書いた。

#!/bin/bash
COMMAND=$*
MIN=`date '+%M'`
HOUR=`date '+%M'`
DAY=`date '+%e'`
MONTH=`date '+%b'`
WEEK=`date '+%a'`
TMP_IN=`mktemp -q /tmp/$0.XXXXXX`
if [ $? -ne 0 ]; then
        echo "$0: Can't create temp file, exiting..."
        exit 1
fi
TMP_OUT=`mktemp -q /tmp/$0.XXXXXX`
if [ $? -ne 0 ]; then
        echo "$0: Can't create temp file, exiting..."
        exit 1
fi
CRON=''
#crontab -l
crontab -l > $TMP_IN
grep -v --regex "^[0-9]\{1,2\} [0-9]\{1,2\} [0-9]\{1,2\} [A-Z][a-z]\{2\} [A-Z][a-z]\{2\} sleep [0-9]\{1,2\}; $COMMAND;$" $TMP_IN>$TMP_OUT
for i in `seq 0 23`
do
        HOUR=$i
        MIN=`expr $RANDOM % 60`
        SEC=`expr $RANDOM % 60`
        CRON="$MIN $HOUR $DAY $MONTH $WEEK sleep $SEC; $COMMAND;"
        echo $CRON >> $TMP_OUT
done
crontab $TMP_OUT
rm $TMP_IN $TMP_OUT
$crontab -l
exit

実際にランダムに実行されるコマンドはpushjob.shの引数となるので今までのジョブでsleep `expr $RANDOM \% 3600`;としていた部分をsh pushjob.shに書き換え 、これを毎日0時0分に走るようにcrontabに追加

$ crontab -e
#0 * * * * sleep `expr $RANDOM \% 3600`; wget http://www.google.com -O -
0 0 * * * sh pushjob.sh wget http://www.google.com -O -

これで毎日0時0分にcrontabが下のような内容のジョブが追加され、過去に行ったジョブは消去される。単純にsleepを挟むよりも無駄なメモリ占有を抑えてジョブが走るはずだ。

9 0 13 Feb Fri sleep 40; wget http://www.google.com -O -
18 1 13 Feb Fri sleep 48; wget http://www.google.com -O -
27 2 13 Feb Fri sleep 57; wget http://www.google.com -O -
35 3 13 Feb Fri sleep 5; wget http://www.google.com -O -
44 4 13 Feb Fri sleep 14; wget http://www.google.com -O -
52 5 13 Feb Fri sleep 23; wget http://www.google.com -O -
1 6 13 Feb Fri sleep 31; wget http://www.google.com -O -
2 7 13 Feb Fri sleep 40; wget http://www.google.com -O -
10 8 13 Feb Fri sleep 49; wget http://www.google.com -O -
19 9 13 Feb Fri sleep 57; wget http://www.google.com -O -
27 10 13 Feb Fri sleep 6; wget http://www.google.com -O -
36 11 13 Feb Fri sleep 14; wget http://www.google.com -O -
23 12 13 Feb Fri sleep 53; wget http://www.google.com -O -
32 13 13 Feb Fri sleep 2; wget http://www.google.com -O -
40 14 13 Feb Fri sleep 11; wget http://www.google.com -O -
49 15 13 Feb Fri sleep 19; wget http://www.google.com -O -
57 16 13 Feb Fri sleep 28; wget http://www.google.com -O -
6 17 13 Feb Fri sleep 36; wget http://www.google.com -O -
15 18 13 Feb Fri sleep 45; wget http://www.google.com -O -
23 19 13 Feb Fri sleep 54; wget http://www.google.com -O -
32 20 13 Feb Fri sleep 2; wget http://www.google.com -O -
41 21 13 Feb Fri sleep 11; wget http://www.google.com -O -
49 22 13 Feb Fri sleep 19; wget http://www.google.com -O -
58 23 13 Feb Fri sleep 28; wget http://www.google.com -O -

前にも述べたようにpushjob.shの引数に与えたコマンドを追記するので、下のように全体を''で括ればランダムに行われるジョブの結果を捨てる様にcrontabに追加することもことも出来る。

$ crontab -e
0 0 * * * sh pushjob.sh 'wget http://www.google.com -O - 2>&1 > /dev/null'

これにより追加されるジョブは下のような感じ

9 0 13 Feb Fri sleep 40; wget http://www.google.com -O - 2>&1 > /dev/null;
18 1 13 Feb Fri sleep 48; wget http://www.google.com -O - 2>&1 > /dev/null;
27 2 13 Feb Fri sleep 57; wget http://www.google.com -O - 2>&1 > /dev/null;
35 3 13 Feb Fri sleep 5; wget http://www.google.com -O - 2>&1 > /dev/null;
44 4 13 Feb Fri sleep 14; wget http://www.google.com -O - 2>&1 > /dev/null;
52 5 13 Feb Fri sleep 23; wget http://www.google.com -O - 2>&1 > /dev/null;
1 6 13 Feb Fri sleep 31; wget http://www.google.com -O - 2>&1 > /dev/null;
2 7 13 Feb Fri sleep 40; wget http://www.google.com -O - 2>&1 > /dev/null;
10 8 13 Feb Fri sleep 49; wget http://www.google.com -O - 2>&1 > /dev/null;
19 9 13 Feb Fri sleep 57; wget http://www.google.com -O - 2>&1 > /dev/null;
27 10 13 Feb Fri sleep 6; wget http://www.google.com -O - 2>&1 > /dev/null;
36 11 13 Feb Fri sleep 14; wget http://www.google.com -O - 2>&1 > /dev/null;
23 12 13 Feb Fri sleep 53; wget http://www.google.com -O - 2>&1 > /dev/null;
32 13 13 Feb Fri sleep 2; wget http://www.google.com -O - 2>&1 > /dev/null;
40 14 13 Feb Fri sleep 11; wget http://www.google.com -O - 2>&1 > /dev/null;
49 15 13 Feb Fri sleep 19; wget http://www.google.com -O - 2>&1 > /dev/null;
57 16 13 Feb Fri sleep 28; wget http://www.google.com -O - 2>&1 > /dev/null;
6 17 13 Feb Fri sleep 36; wget http://www.google.com -O - 2>&1 > /dev/null;
15 18 13 Feb Fri sleep 45; wget http://www.google.com -O - 2>&1 > /dev/null;
23 19 13 Feb Fri sleep 54; wget http://www.google.com -O - 2>&1 > /dev/null;
32 20 13 Feb Fri sleep 2; wget http://www.google.com -O - 2>&1 > /dev/null;
41 21 13 Feb Fri sleep 11; wget http://www.google.com -O - 2>&1 > /dev/null;
49 22 13 Feb Fri sleep 19; wget http://www.google.com -O - 2>&1 > /dev/null;
58 23 13 Feb Fri sleep 28; wget http://www.google.com -O - 2>&1 > /dev/null;

この方法の問題点は、毎日0時0分crontabを書き換えるジョブがcronによって走らされてしまうため、このタイミングでcrontabを編集していると内容が変わってしまう可能性があることだ。

  1. シェルスクリプト 引数 - Google 検索
  2. bashで始めるシェルスクリプト基礎の基礎(1/2)
  3. 競合状態を避ける
  4. 【 mktemp 】 適当なファイル名の空ファイルを作成する:ITpro
  5. シェルスクリプト テンポラリ - Google 検索
  6. Manpage of CRONTAB
  7. crontab - Google 検索
  8. 【 crontab 】 プログラムを定期的に実行するcrondの設定ファイルを編集する:ITpro
  9. crontab 表示 - Google 検索
  10. 日付を取得する - UNIX & Linux コマンド・シェルスクリプト リファレンス
  11. linux date フォーマット - Google 検索
  12. Regular Expressions in grep
  13. grep regex - Google 検索
  14. シェルスクリプト入門 [制御構文]
  15. シェルスクリプト for 文 - Google 検索
  16. ユーザ crontab - Google 検

さて、同じ事を行うためにatは秒単位でもいけそうだという話を聞いて、crontabににatジョブを追加するような方向性も考えたのだが、余分にatdが走るし、atは5分毎にatrun(8)を呼び出すcrondという話もあるし、やめておいた。まぁ実際秒単位指定の出来るat入れるの面倒そうだったのでやめちゃっただけだけど。

  1. cron at 実装 5分毎 - Google 検索
  2. 【 at 】 指定時刻にジョブを実行する:ITpro
  3. at(1)
  4. at ジョブ 秒 - Google 検索
  5. at ジョブ 秒 linux - Google 検索
  6. cron at 秒単位 - Google 検索
  7. timespec - Google 検索

sourceで内容を現在のシェルに読み込む

bashrcを書き換えたときにその内容をログアウトせずに反映させるにはsourceまたは.(ピリオド)コマンドを使う。このコマンドはシェルスクリプトなどでも使えて、共有されるべき変数などの情報を書いたファイルをシェルスクリプト中で読み込む場合なんかにも使われる。

$ source ~/.bachrc
$ . ~/.bashrc

コマンドの再実行

上下矢印で出来ることはよく知られている。上矢印と下矢印がそれぞれctrl-pとctrl-nに対応している。大きく手を動かなさくてもよくなりいい感じ。これを知ると日本語キーボードのctrlの位置が本当に気になるようになってしまう。

コマンドライン履歴を見る。

$ history

1カラム目にコマンドライン履歴の履歴番号が出るので、再実行する場合は!に続けて当該コマンドの履歴番号をタイプする。exit後に保存されるコマンドライン履歴の量は決まっているが、exitするまでは履歴番号とコマンドの対応関係は変わらない。また、ほとんどの場合流れるのでlessにパイプすることが多い。

$ history | less

または、grepにパイプする。

$ history | grep tar

これで昔やったtarの履歴を抽出できる。

履歴置換で直前のコマンドを実行

直前のコマンドは!!で再実行できる。でも所詮置換しているだけなので、オプション追加とかもできる。例えば、ls -lで流れてしまった部分を逆順にしてみるとか。

$ ls -l
$ !! -r

この技は!から始めることで履歴置換モードになることによっている。!!は!-1と同じ。2つ前のものは!-2でOK。詳しくはman参照。また、!のあとに文字列を続けることでコマンドライン履歴のなかから最も近いコマンドを再実行できる。

$ pwd
$ echo hoge
$ !p

このようにした場合、2つ前に実行したpwdが!pとすることで実行される。さらに!?のあとに文字列を続けると、其の文字列を含むコマンドライン履歴を実行できる。

$ ls -l
$ echo ls
$ !?ls

とすると、echo lsが実行される。コマンドも引数もオプションも区別されない。

履歴置換で実行されるコマンドの確認

:pを付ける。

$ ls
$ !!:p

履歴置換が実行されて、当該の履歴置換で実行されるコマンドが表示される。例ではlsと表示される。

ブレース展開

$ mv hoge hoge.org

というのは、

$ mv hoge{,.org}

と同じ。manのブレース展開の部分を見るとよくわかる。ブレース{}の中身をカンマで区切って展開する。例えばa{b,c,d}eがabe ace adeと解釈される。今回はhoge{,.org}なのでhoge hoge.orgと展開される。と言うことで、初めに書いたのと同じ。

$ mv /path/to/hoge /path/to/hoge.org
$ mv /path/to/hoge{,.org}

上の2つは同じ意味だが、hogeまでのパスが長かったりするとディレクトリ移動しないで長いパスを書かねばならずブレース展開に多少の意味がある。

カーソル移動とコマンドライン編集

ctrl-fとctrl-bで左右にカーソルを動かせる。ctrl-aで行頭、ctrl-eで行末。でもctrl-aはscreenのキーバインドとかぶる。特に行頭移動は使える。動かして編集できる。ctrl-dでカーソル位置の文字を削除できる。削除にも種類があって、ctrl-kで行末まで削除、ctrl-wで行頭まで削除。さらにctrl-tで文字入れ替え。

画面クリア

ctrl-lで画面クリア。色々と人に言えないことをしているときに。

直前コマンド最後の引数

$ echo hoge
$ ls !$

これは以下と同じ。

$ ls hoge

直前コマンドの全ての引数

$ echo hoge fuga
$ ls !*

これは以下と同じ

$ ls hoge fuga

組み合わせるとタイプミスに依る間違いが減る。rmするファイルをlsで確認してから削除とか出来る。

$ ls *.txt
$ rm !*

vim風のキーバインドをbashで

vim使いはCtrlとか使わないんだよ。隙あらばEscとか押している自分がいる。とにかくbashでカーソルの単語もどりとかをVim風に変える。

$ echo set -o vi >> ~/.bshrc

一部変更して実行

$ grep hoge hoge.txt
$ ^o^a

これは以下と同じ

$ grep hage hoge.txt

oをaに置換するが、一箇所だけのようだ。全部置換したい場合は

!!s:/o/a/

これは以下と同じ

$ grep hage hage.txt

[bash] 2回連続の同一コマンドの場合に2回目をヒストリに登録しない方法

.bash_history に重複したコマンドを記録させないようにするというのは嘘で2回同じコマンドを入力した場合にのみ2回目のコマンドをヒストリに登録しないようにする。注意したい点は、重複するコマンドをヒストリに登録しない、のではなく、続いて2回同じコマンドが入力された場合は2回目のコマンドをヒストリに登録しない、ということだ。つまり下のようにコマンドを打つと、全てがコマンドヒストリに登録される。

$ ls
$ cd hoge
$ ls

これに対して次のようにした場合ははじめのコマンドのみがコマンドヒストリに登録される。

$ ls
$ ls
$ ls

つまり、比較対象は1つ前のコマンドのみということだ。下のようにすれば、入力したコマンドと一つ前のコマンドとを比較して、同じなら記録せず、異なっていれば記録する。ということになる。

$ echo export HISTCONTROL=ignoredups >> ~/.bashrc

比較は一つ前のコマンドについてのみなされるということは、すでに登録されたものに対してはされないということだ。

たとえば ~/.bash_history にある重複行を削除したいときは下のようにすればいい。

$ perl -lne 'push @a,$_; END{ @s=grep(!$t{$_}++,@a); print map{"$_\n"}@s}' ~/.bash_history

こうすれば、 ~/.bash_history に登録されている順番を崩さずに重複行のみを削除できる。下のようにしてもよいが、

$ sort ~/.bash_history | uniq

この場合は順番が崩れる。~./bash_history の重複行を削除するということに意味があるかは判らない。なぜなら、入力したコマンドはその順番も含めて意味のあるものだと思うから。

grepでコメントと空行を取り除く

-vは結果の反転。-eの後にパターンを続ける。この場合なら、ファイルに含まれる行の内、「#にマッチしない行」と「空行にマッチしない行」を抜き出すことになる。設定ファイルの多くは#をコメントアウト、空行を読み捨てとして扱うのでこれを通せば設定項目のみを抜き出すことができる。

$ grep -v -e '#' -e '^$' /etc/ppp/peers/provider

grepで大文字小文字を区別せずにマッチング

/var/log/messagesのなかからcronのログを見たい場合に、cronとCRONと書かれている部分がある。それらを一括してみたい場合にはgrepの-iオプションを有効する。

# grep -i 'cron' /var/log/messages
  1. grep 大文字 - Google 検索
  2. 10.4 文字列の検索 -- (grep)
  3. grepの簡単な使い方
  1. ハッカーに一歩近づく Tips
  2. BASH

lessでstderrを見る

stderrに渡された内容は単純にパイプ下だけではlessに渡されない。ということで。

$ ./hoge | less

とするかわりに

$ ./hoge 2>&1 | less

とすればいい。

ソーシャルブックマーク

  1. はてなブックマーク
  2. Google Bookmarks
  3. del.icio.us

ChangeLog

  1. Posted: 2003-12-27T16:18:30+09:00
  2. Modified: 2003-12-27T14:19:46+09:00
  3. Modified: 2009-04-07T16:43:48+09:00
  4. Generated: 2017-07-10T23:10:59+09:00