固定長データを生成するシェルスクリプト

Posted on 2021/02/06

TOC

はじめに

バッチファイルは敬遠していたのですが仕事で関わる機会があり、毎度のことながら書き方を忘れてしまうのでちょっとまとめようと思います。 今回は何らかのインプットデータから固定長のデータを生成するシェルスクリプトを書くためのノウハウをまとめました。 バッチファイルを最低限書ければ良いという意気込みなので内容は緩めです。

前提

Mac(Catalina)でbashで書いています。最近はMacのはデフォルトシェルがzshに変わってるみたいですが、bashで凝ったことしないので特に設定せずにできる範囲で書きます。

また、この後説明する書き方を取り入れたサンプルファイルは以下にありますので参照ください。
https://github.com/somurieengieer/BashBatchSample

シェルの基礎知識が全然ない

以下サイトは詳細にシェルを説明しているので初心者の人には結構おすすめです。

https://language-and-engineering.hatenablog.jp/entry/20110617/p1

こちらもおすすめ。シェルスクリプト作成時の例 https://language-and-engineering.hatenablog.jp/entry/20101028/p1

Tips

csvファイルの読み込み

csvファイルを読み取る際にはreadを使うと早いです。
条件によって変わるようですが、大まかにread > set > cut, awk のような認識で良さそうです。

cat "./${INPUT_FILE}" | while IFS=, read -r id name age country
do
    # 各行の処理
end

参考:https://qiita.com/hasegit/items/5be056d67347e1553f08

前後に半角スペースを含んだ固定長ファイルを読み取る

前後に半角スペースを含んだ固定長ファイル「while IFS= read -r 変数名」で読み込むと空白を削除せずに読み込めます。
タブやバックスラッシュも変換されずにそのまま読み込めます。

cat "./${INPUT_FILE}" | while IFS= read -r id name age country
do
    # 各行の処理
end

参考:https://linuxfan.info/post-2024

文字の出力

前後に半角スペースを含んだ文字列を出力する歳は以下のように書きます。”“で括らないと前後の半角スペースが削除された値が代入されてしまいます。 また、variantの中身が空文字である場合エラーになるので空になる可能性がある変数は”“で括って扱います。

out_line="$(varient)"

トリミング方法

長い文字列を一部カットするには以下のような方法があります。
以下の例は冗長に書いてありますが、wc -m、if、cut、${name:0:5}の書き方を組み合わせるとトリミングは網羅できると思います。 文字数を見てカットするので、半角と全角が混在しないように注意してください。

name_count=`echo -n ${name} | wc -m`
# name_count=`${#name}` # こんな書き方もできる
if [ $name_count -ge 5 ]; then
  out_line+="`echo $name | cut -c 1-5`"
else
  name="${name}     "
  out_line+=${name:0:5} # 右側に全角ブランクを詰めて0文字目から5文字目をトリミング
fi

連続する半角スペースを出力する

連続する半角スペースは以下のように生成できます。

out_line="$(printf '%3s')"

0詰めして出力する

0詰めは以下のように生成できます。

out_line=`printf %03d ${id}`

ファイルのクリア

OUTPUT_FILEを一旦クリアするには以下のような書き方ができます。
※書き方は色々あるがbashでは以下が一番完結(zshだとこの書き方はNGらしい)

> ${OUTPUT_FILE}

性能計測

以下のように実行すると処理時間を計測できる

$ time ./process.sh 

real	0m0.042s
user	0m0.011s
sys	0m0.025s

[〜]と[[〜]]の違い

ややこしいのですが、if文等を調べるとif [ $a < 3 ]やif [[] $a < 3 ]]のような書き方が出てきます。 [〜]と[[〜]]で動きが異なることがあるので注意が必要で、結論としてはbashでは[[〜]]を使うのが無難です。
理由としては、[〜]はビルトインスクリプトであるのに対して[[〜]]はbashの構文であること、bashをあまり使わないユーザーにとって[[〜]]の方が条件文がわかりやすいこと(<>や&&、||が使えること)などが挙げられます。

$ which [
/bin/[
$ type [
[ is a shell builtin
$ type [[
[[ is a shell keyword

参考:https://qiita.com/Riliumph/items/f07272fa9b0834032a9d 参考2:https://qiita.com/Ping/items/f9b5175085026462b082

別プロセスでシェルを実行する

(〜)で括ると別プロセスでシェルを実行することができる。>でリダイレクトして使うことが多い。

dir="/bin"
(cd $dir; pwd)> test
cat test      # /binが出力される

フラグを使う

bashにはbool型のようなものはないので、変数が空か否かという条件で表すことが可能です。
-nは長さが0である場合、-zは空である場合にtrueを返します。

flag=1
if [[ -n "$flag" ]]; then
  echo "flag is on"
fi
if [[ ! -n "$flag" ]]; then
  echo "flag is off"
fi
if [[ -z $flag ]]; then
  echo "flag is off"
fi
if [[ ! -z $flag ]]; then
  echo "flag is on"
fi

※上記の書き方はファイル最上部に#!/bin/bash -uと書いていると使えない(unbound variableエラーになる)ので注意 ※ファイル最上部に#!/bin/bash -uを書いている場合でも使えるif [[ -v 変数名 ]]というものもあるが、Macのbashだと使えない??(man [ を見てみても-vのオプションが見当たらない)