大阪大学医学部 Python会

Now is better than never.

シェルスクリプト入門(2)

2018-11-16(Fri) - Posted by 水野 in 技術ブログ    tag:Shell script

Contents

    ※ 編集注:前作シェルスクリプト入門(1)の続編です。まだ読み終わってない方はそちらを先にどうぞ。

    テーブルファイルの操作と言えばRです。 (※ 編集注:Pythonでもできます。) が、そこまでbashで書いてきたのに、R呼び出して変数再設定して…面倒くさいから嫌だ!! ってことないでしょうか。

    Rの機能を代替するのは無理ですが、せめてほんの少し、 例えば、テーブル中の値をそれぞれbashの変数に入れれたら、 Rを呼び出さずに完結できたりします。

    要は、$ mat_i_j=i行j列目の要素で変数を格納していきます。

    対象ファイル名を、table.txtで、タブ区切り、中身は以下みたいの。

    a    b    c
    1    2    3
    d    e    f
    

    これの各要素を、$mat_i_j に格納していきます。 一例として、以下のスクリプトでできます。 配列つかったりしてもっといいスクリプトもきっとあります。

    bashは汚いスクリプトでもたいてい速く動いてくれるので成長しないですね。

    #!/bin/bash
    row=0 ## 行番号に使う変数の設定。
    while read x1 x2 x3 ## 解説①
    do
        row=`expr $row + 1` ## 1行ずつ読んでくので、1ずつ足していく。
        for col in `seq 1 3` ## 今読んでる行での列番号をfor文で回す。
        do
            eval mat_${row}_${col}=`eval echo \\$x${col}` ## 解説②
        done
    done<table.txt
    
    ### 変数mat_$i_$j を使った処理が続く。
    

    時間のある人用に解説です。 少し小技があります。

    1) while readの使い方

    よく見かけるのは、catからパイプでwhile readにつなぐやつです。

    cat table.txt | while read x1 x2 x3
    

    ファイルを一行ずつ読んで、各列の要素をreadの後ろに置いた変数名(x1,x2,x3)に格納していきます。変数名の個数は任意です。ここでは列の数に合わせてます。 各行ごとに、dodoneの間で、$x1,$x2,$x3が使えるようになります。

    が、しかし、パイプでwhileに入ってしまうと、パイプ内はパイプ内で完結するため、 do~doneの中で変数操作をしても、doneの後では、その中身が空っぽになっちゃいます。

    これを回避するためには、パイプを使わないということで、リダイレクトで、 doneの後ろに

    done<table.txt
    

    を書きます。 読みにくいですよねー。

    2) evalの使い方

    今回のmat_$i_$jのように、変数名に変数($i,$j)を使って値を格納する時には、evalを使います。 evalは、その後に書いた文をもってきて、bashスクリプトとして評価(evaluate)します。

    なので、i=2, j=3 として、

    eval mat_${i}_${j}=aaa

    は、 mat_2_3=aaa をスクリプトとして評価しろ、ということになり、 mat_2_3という変数名に、aaaが入ります。 (ちなみに、どこまでが変数名か分からなくなる状況では、変数名を{}でくくります。ここではiとj。)

    変数を使った変数名の中身を出力させる場合には、echoとevalを組み合わせます。 col=1として、

    eval echo \$x${col}
    

    と書けば、まず、eval$colの中身を出してからスクリプトとして評価するので、 (xの\(には\がかかってるので無視されます) echo $x1 と書いていることになり、通常のechoで書いたように、$x1の中身が出力されます。 xの\)にかかってた\は、evalでの評価で消費されて、echo内にはなくなります。ここ重要。

    やりたいことは、eval echo で出してきた値を、変数を使った変数名に格納することです。

    eval mat_${i}_${j}=aaa

    aaa部分にeval echoを直接もってきても、機能しません。 eval echoのままでは、まだ値ではなくスクリプトだからです。

    なので、そのスクリプトを実行させて値を出力させたものを、aaaの部分に書きます。 そういう時は、アクサングラーブ(` シフト押しながら@)でくくります。

    アクサングラーブは、その中にあるスクリプトの実行結果を出力します。 なので、

    test=`echo $k`
    

    とすれば、$kの中身が出力されて、testに格納されます。 rowに1ずつ足していくとこでも使ってます。よく見る方法です。 for文使わなくていいので、見た目がすっきりします。

    では、

    eval mat_${i}_${j}=`eval echo \$x${col}`
    

    と書けば、mat_2_3$x1の中身が代入されるはず! となるのですが、不完全です。

    アクサングラーブでの評価で、\が一個消費されてしまうので、実行するeval echoの文が、

    eval echo $x${col}
    

    となるので、evalでの評価時に$xの中身(設定してないので空っぽ)と$colが出されてしまって、 echo (空)1 となって、最終的に、mat_2_3には$x1ではなく、$colの中身である1が代入されてしまいます。

    なので、

    eval mat_${i}_${j}=`eval echo \\$x${col}`
    

    と、\を一個増やして書けば、目的達成です。 めでたしめでたし。

    ※ 編集注 : 記事はめでたく終わりましたが、アクサングラーブがマークダウンと干渉してしまいました。試行錯誤の結果、ところどころテキストが混じって読みにくくなってしまいました。すみません。