Mazn.net

やってみて 調べてみて 苦労しなけりゃ 箱は動かじ

bash

パイプを使ったループの中で使用した変数をループ外で参照できない@bash

bashでパイプを使って以下のような処理をすると、ループ内の変数の値をループ外で参照できないようです。

#!/bin/bash
HOGE="hoge"
echo -e "$HOGE" | while read I ; do
    HOGE="$HOGE $I"
    echo "$HOGE"
done
echo "$HOGE"

上記を実行すると結果は以下のようになります。

hoge hoge
hoge

原因はパイプを使うと、パイプの先は別プロセスで処理されるためのようです。つまり上記では、呼び出し元の$HOGEに入っている"hoge"は呼び出し先で参照でき、結果は"hoge hoge"になりますが、ループの外、つまり呼び出し元では呼び出し先の処理が反映されないので、"hoge"になります。

なので、対策は面倒ですが一度ファイルに書き出すのがベストな気がします。

#!/bin/bash
HOGE="hoge"
echo -e "$HOGE" | while read I ; do
    HOGE="$HOGE $I"
    echo "$HOGE" > tmp
done
HOGE=`cat tmp`

スペースの入ったディレクトリ名やファイル名をスクリプトで処理する@Linux

findコマンドでスペース入ったディレクトリやファイルを検索し、その結果をforで処理しようとすると、スペースが区切り文字になってしまい、うまく処理できません。

その時はreadを使って以下のようにするとうまく処理できます。

tar.gzファイルを処理する場合
$ find . -name "*.tar.gz" | while read file; do echo "$file"; done

evalで変数名を変数にする@bash

シェルスクリプトで変数を使用していると、変数名を変数にしたい場合が出てきます。

しかし、以下のようなシェルを書いてもエラーになってしまいます。

#!/bin/bash
HOGE_1=1 
HOGE_2=2
for i in 1 2; do
echo ${HOGE_$i}
done
?$ ./hoge.sh
./hoge.sh: line 6: ${HOGE_$i}: bad substitution

そこで、evalの登場です。evalは、引数を一度評価してから実行します。つまり、

HOGE="echo hoge"
eval $HOGE

を実行すると、evalは$HOGEの内容を評価し、"echo hoge"に変換します。さらにこれを実行するので、hogeと表示されます。これを使えば変数名を変数にできます。具体的には、以下のようにします。

#!/bin/bash
HOGE_1=1 
HOGE_2=2
for i in 1 2; do
eval echo '$HOGE_'$i
done

実行すると、

$ ./hoge.sh
1
2

となりHOGE_1とHOGE_2の内容が表示されます。めでたしめでたし。

シェルスクリプトでファイル内の文字列を置換する

== 2012/11/8 追記 ==

以下ではedコマンドでの文字列置換を紹介していますが、sed に -i オプションをつけると、ファイル内の文字を置換できるようです。


あるファイルをsedやtrで文字列置換したあと、同じファイルにリダイレクトすると空になってしまいますよね。そこでテンポラリファイルなどにリダイレクトした後に、mvするという方法がまず考えられますが、その分やることが増えて面倒です。

そこでedというラインエディタを使うと、ファイル内の文字列をコマンドラインで置換することができます。ラインエディタとはviやemacsといったエディタみたいなものですが、行単位でテキストを編集するためにこのように呼ばれます。それに対してviやemacsはスクリーンエディタと呼びます。

ちなみに、viやvimで文字列を置換する場合(hogehogeをaaaaaにする場合)

:%s/hogehoge/aaaaa/g

といったコマンドを使いますよね。これが実はedのコマンドexコマンドと呼び、edのコマンド由来であるため、viのexコマンドを使える人はedもすぐに使えます。

よって、シェルスクリプトで置換するには、以下のようにすると置換できます。

$ echo -e "%s/hogehoge/aaaaa/g\\nw" | ed - ファイル名

ここでおや?って思った人もいると思います。コマンドの最後に\nwがあります。"w"はviで変更を保存するときと同じで、edもあくまでエディタなので、この保存コマンドが必要になります。"\n"は改行です。edは1行に1コマンドなので、置換するコマンドと保存するコマンドをこの改行で区別しています。echo に -eがあるのはこの改行文字列をechoで扱えるようにするためです。edの"-"は通常の端末メッセージを表示しないようにするオプションです。

上記ではechoでコマンドを作っていますが、リダイレクト"<"でコマンドの書かれたファイルを読み込んで実行することも可能です。

$ ed - ファイル名 < コマンドの書かれたファイル

また、ヒアドキュメントを使って以下のようにすることも可能です。

$ ed - << END
%s/hogehoge/aaaaa/g
w
END

hogehogeにマッチする行を削除するには、以下のようにすればOKです。これもviのコマンドと一緒ですね。

$ echo -e "g/hogehoge/d\\nw" | ed - ファイル名

なお、シェルスクリプトでこのような処理を行う場合、"\"や"$"などがシェルで解釈されてしまうので、エスケープが必要になります。

(例)

$ echo -e "g/\\$aaaaa/d\\nw"
g/$aaaaa/d
w

$ echo -e "g/\\\\\\\\1aaaaa/d\\nw"
g/\\1aaaaa/d
w

電車の運行情報をチェックしてメール送信をするシェルスクリプト@bash

電車の遅延や事故などの運行情報を公開しているサイトがありますが、使用している路線に何か変化があった場合に携帯にメールを送るサービスは有料ばかりなので、ちょこっとシェルを書いてみました。

24時間起動しているサーバ環境があることが前提です。また、lynxがインストールされている必要がありますし、もちろんサーバからメールを送信できる環境でないと動きません。

train.sh

#!/bin/bash
# written by mazn [ https://www.mazn.net/ ]

# 関東の鉄道情報を使用します
urls=(http://transit.goo.ne.jp/unkou/kantou.html)
# 送信先 (複数指定可能)
mailto=("@docomo.ne.jp" "@docomo.ne.jp")
# 差出人
mailfrom="train info <train_info@dummy>"

filename=/tmp/train.txt
sendflg=0
d=`LANG=C date`
contents=""

function sendMail {
        for to in ${mailto[@]}; do
                header=""
                header="${header}Subject: Train info\\n"
                header="${header}From: ${mailfrom}\\n"
                header="${header}To: ${to}\\n"
                echo -e "${header}\\n${contents}" | nkf -j | sendmail -t -i
        done
}

for i in ${urls[@]}; do
        # データ取得
        echo $d > $filename
        lynx -dump $i -useragent="" -assume_charset=euc-jp -display_charset=euc-jp -width=1000 | nkf -w80 >> $filename

        for j in ${lines[@]}; do
                tmp=`egrep "   ../.. ..:.. .{0,10}$j.{0,10} " $filename | sed "s/:blank:*//"`

                if [ "x$tmp" = "x" ]; then
                        continue
                fi
                if [ -f /tmp/$j ];then
                        oldinfo=`cat /tmp/$j`
                else
                        oldinfo=""
                fi

                if [ "x$tmp" != "x$oldinfo" ]; then
                        contents="${contents}${tmp}\\n"
                        sendflg=1
                fi
                echo "$tmp" > /tmp/$j
        done
done

if [ "$sendflg" == "1" ]; then
        contents="${contents}\\nhttp://gw.mobile.goo.ne.jp/gw/http://transit.goo.ne.jp/mobile/unkou/kantou/index.html\\n$d"
        sendMail
fi

情報はgooから取得しています。ここでは関東の鉄道情報を対象にしていますが、関東以外を知りたい場合は、変数urlsにgooの対象の地域のURLを追加するか、書き換えます。前回チェック時の情報を/tmpに保存していて、変化があったらメールを送信します。

変数linesには、チェックしたい路線名を入れます。単なるegrep用文字列なので、gooのサイトに掲載される路線名をそのまま記述します。

mailtoには自分のアドレスを入れます。mailfromは適当に送信者情報を入れてください。

あとはこれをcronに登録して、定期的に起動すればOK

/etc/crontab

*/30 * * * * root /root/train.sh

bashで配列のエクスポートができない@bash 3.2

bashにおいて、サブシェル(呼び出し先の別のシェル)内で呼び出し元の変数を参照しようとする場合、export または declareに-xオプションを使用する必要がありますが、配列変数も同様にexportしても参照できません。

例えば以下の2つのシェルを書きます。

hoge.sh内容

#!/bin/bash
declare -a -x HOGE=("hoge1" "hoge2")    # 配列変数
declare -x FOO="foo"                    # 通常の変数
./foo.sh

foo.sh内容

#!/bin/bash

echo "HOGE="$HOGE
echo "FOO="$FOO

実行すると、下記のように配列の内容を表示できません。

# ./hoge.sh
HOGE=
FOO=foo

これは単純にbashが配列のexportをサポートしてないからのようです。なので、どうしても配列を"source"コマンドまたは"." コマンドで実行してあげる必要があります。

hoge.sh内容(source版)

#!/bin/bash
declare -a -x HOGE=("hoge1" "hoge2")
declare -x FOO="foo"
source ./foo.sh

実行すると、下記のようにHOGEの内容が表示されます。

# ./hoge.sh
HOGE=hoge1
FOO=foo

もちろんsourceや"."で実行した場合、サブシェルではなく同じシェル上で実行されるので、fool.sh内での変数宣言がhoge.sh内でも見られるようになりますので、変数名のぶつかりに注意する必要があります。

シェルで逐一時間表示@bash

デバッグを目的に、 逐一コマンド実行時に時間を表示させたい場合があります。

$ trap "date" DEBUG

これでコマンド実行時に日付が表示されます。

ちなみに、無効にするには

$ trap  DEBUG

を実行します。

ログアウトしてもプロセスを止めないようにする@bash

bash上で何かプログラムを走らせている時に、ログアウトしてもプロセスを動かし続ける方法として、いくつかまとめてみました。

  1. nohupを使う コマンド起動時に、
    $ nohup ./プログラム名 &
    としてログアウトする
  2. screenを使う コマンド起動前に、
    $ screen
    のコマンドを実行しておき普通通りプログラムを実行する
    $ ./プログラム名
    その後、"Ctrl-a" "d"  を押してscreenからぬけ、ログアウトする。
  3. disownを使う コマンド起動後
    $ ./プログラム名
    Ctrl-z でサスペンドして、バックグランドで実行
    $ bg
    その後disownを実行してログアウトする
    $ disown

1,2はプログラム起動時に気をつけないといけないですが、実行してしまった後にどうにかしたい場合は3を使う必要があります。

標準出力は、1はnohup.out にリダイレクトされます。2はscreenという仮想端末上で実行されていますので、後でログインしてscreenコマンでアタッチすることで同じ画面に戻ることができます。3は ログアウトすると捨てられてしまうようなので、予め出力をリダイレクトしたりteeでファイルに保存しておく必要があります。

bashで切り取った文字列の履歴をさかのぼって貼り付ける

bashで、Ctrl-kやCtrl-wで切り取りとった文字列をCtrl-yで貼り付けすることは少しbashに慣れた人ならよくやると思いますが、Ctrl-yで貼り付けした直後に M-y (Alt-y)を押すと、履歴を遡って貼り付けすることができます。

例えば、コマンドラインに以下の文字列があり、カーソルの位置が4の後ろにあるとします。

$ 1234 

この状態で、Ctrl-w を押すと1234が切り取られ、コマンドラインの文字は無くなります。

$  

この状態で、5678を入力します。

$ 5678 

また、Ctrl-w を押して、5678を切り取ります。

$  

この状態で、Ctrl-y を押すと、切り取った5678が貼り付けられます。

$ 5678 

この直後にAlt-yを押すと、5678の前に切り取られた1234に置き換えられます。

$ 1234 

bash使用時にCtrl-dでログアウトさせない

bashではCtrl-○でいろいろな機能が使用できますが、たまに間違って押してしまうと困ってしまう機能があります。

以前紹介した、Ctrl-sの端末ロックを無効にするもその一つですが、 今回はCtrl-dでのログアウトです。Ctrl-Sの端末ロックはCtrl-qで解除できるのでまだよいですが、Ctrl-dは有無も言わさずログアウトしてしまうので、操作ミスが致命的になる場合があります。

そこで Ctrl-dを押しても何回か警告を表示する方法を紹介します。設定は簡単で、環境変数 IGNOREEOFに警告回数を設定するだけです。

$ IGNOREEOF=3

こうすることで、Ctrl-d押下時に、以下のように3回警告が表示され、4回目でログアウトします。

$ Use "logout" to leave the shell.
$ Use "logout" to leave the shell.
$ Use "logout" to leave the shell.
$ logout

ちなみに上記はsshログインで、コンソール上では"logout"ではなく"exit"と表示されるようです。

簡単バックアップシェル

サーバ管理していると、ちょっとしたデータを定期的にバックアップしたいことがあります。私が普段使用している簡単バックアップのbashのシェルを紹介します。

#!/bin/bash
BACKUP_NAME=`date '+%y%m%d.tar.bz2'`
BACKUP_DIR=/home/backup/
BACKUP_FILE=/var/www

cd $BACKUP_DIR
tar cvfj $BACKUP_NAME $BACKUP_FILE
find $BACKDUP_DIR -type f -daystart -mtime +15 -follow |xargs /bin/rm -f

上記例では、ホームページ領域の/var/wwwをtarで固めてBACKUP_DIRで指定したディレクトリ/home/backupにバックアップしています。tarファイル名は、バックアップ時の年月日としています。

これを毎日cronで実行してあげればよいのですが、このままだといつかはディスクが足らなくなってしまうので、最後のfindで/home/backup内の15日以上古いファイルは削除するようにしています。

今回はtarで、あるディレクトリをまるごと固めていますが、tarではなくcpでファイルをコピーしたり、データベースをバックアップするコマンドをたたいたりすることでいろいろと対応できます。

bashでの配列操作

あまりbashの配列操作を書くことがないから覚え書き。

定義方法は "変数名=(内容をスペース区切り)" です。

my_array=(a b c d)

もちろん各々の要素に個別に代入することも可能です。

my_array[0]=a
my_array[1]=b
my_array[2]=c
my_array[3]=d

配列の全要素を取り出すには@を使用。

for i in ${my_array[@]}; do
        echo $i;
done

ちなみにbashの配列はexportできないので、注意が必要です。

bashで配列のエクスポートができない@bash 3.2

プログラミング言語のベンチマーク

プログラミング言語のベンチマークサイトを発見

http://shootout.alioth.debian.org/gp4/benchmark.php?test=all〈=all

1位 gcc 2位 g++ … 4位 Java6 … 17位 Python … 48位 Ruby

といった感じ。Rubyちょっと勉強してみようと思ってたけど、なんかやる気失せた。

最近Groobyにも注目してるのだけど、どうなんだろ。

=追記=

JVM上で動くスクリプト言語を比較しているサイトを発見

http://yher2.blogspot.com/2006/10/groovy-rhino-jython-jruby.html

JRubyが本家Rubyよりも早くなったという記事をどこかでみたが、もともとRubyってめちゃくちゃ遅いようで、あまり自慢にならない気がします。Groovyはまだまだ発展途上なので、今後の開発に期待です。Rhino はJVM上で動くJavascriptです。速いですねぇ~!!

このブログについて
プライバシーポリシー・お問い合わせ等
購読する(RSS)
記事検索
アーカイブ
カテゴリー
  • ライブドアブログ