はじめに
普段はC++の記事を投稿しています。
しかし
bashでもUTF-8 with BOMなファイルを作りたい - Qiita
http://qiita.com/yumetodo/items/38e17f6f5a8ef9860f5b
に続き2日連続投稿だ、おおっと・・・?
動機
bashでもUTF-8 with BOMなファイルを作りたい - Qiita
http://qiita.com/yumetodo/items/38e17f6f5a8ef9860f5b
の続き。どうにかそれらしいShellScriptを書いた。
しかし重い。それくらい重いかというと、実行になんとまさかの30分かかる。
私「いくらなんでも30分とか動画のエンコードじゃあるまいし待てるか!」
・・・あ、WindowsのMSYS2のBash上での話です。ssh越しに某サーバーのCentOS7のBashで動かしたところ3秒くらいで終わりました。
原因は、まあWindowsがfork()
持ってないでいでしょ?ということにしておきます。
具体的に重かった部分
#!/bin/bash
# @param input_file input file name
# @param prefix C-Preprocesser-Macro-Function name
# @param need_double_quote_index...
function convert_csv(){
# argument
local -r input_file=$1
local -r prefix=$2
shift 2
local need_double_quote_index=($@)
local -r need_double_quote_index_len=${#need_double_quote_index[@]}
#write BOM
echo -en '\xef\xbb\xbf'
local line_string
local is_first_line=1
while read -r line_string; do
if (( 1==is_first_line )); then
is_first_line=0
echo "//PREFIX,${line_string:0:-1},POSTFIX"
else
local IFS_BACKUP=$IFS
IFS=','
local elements=($line_string)
IFS=$IFS_BACKUP
local elements_len=${#elements[@]}
local i=0
local j
echo -en "\rconverting ${input_file}... id ${elements[0]}" >&2
# sleep 3s
for (( j=0; j < elements_len; j++ )); do
if (( i < need_double_quote_index_len && j == need_double_quote_index[i] )); then
# ダブルクオートで囲う必要がある時
elements[$j]="\"${elements[$j]}\""
(( i++ ))
else
elements[$j]=$(echo "${elements[$j]}" | sed -e 's/\//./g')
fi
done
echo "${prefix}(,$(IFS=,; echo "${elements[*]}"),)"
fi
done < <(iconv -f cp932 -t UTF-8 "${input_file}")
echo -en "\rconverting ${input_file}...done.\n" >&2
}
echo "converting csv..."
convert_csv './ships.csv' 'SHIP' 1 > 'KCS_CUI/source/ships_test.csv'
convert_csv './slotitems.csv' 'WEAPON' 1 2 > 'KCS_CUI/source/slotitems_test.csv'
echo "done."
ShellScriptではあまり見ない[要出典]二重ループの中にある
elements[$j]=$(echo "${elements[$j]}" | sed -e 's/\//./g')
の部分です。
外部コマンド呼び出しだし、$( )
はサブシェル呼び出しだし、パイプ使っているし、こんなもんをfork()
がないWindowsで実行すればそりゃ重いわな。
いやそうは言っても文字列置換といえば
やっぱり文字列置換といえばsed
コマンドですよね?[要出典]ShellSciptなんかめったに書かないWindowsユーザーの私でも知ってるんだから間違いない。[独自研究]
それ、変数展開でできるよ
シェルスクリプト高速化のツボ - 新・日々録 by TRASH BOX@Eel
http://d.hatena.ne.jp/eel3/20141026/1414292281
どうしてもループ構文を使う場合は、ループ中で外部コマンドを使わず、内部コマンド(ビルトインコマンド)で実現できないか検討すること。
とのことなので、頑張って初心者なりに探したところ
bashで変数内文字列の一部を置換する - 元RX-7乗りの適当な日々
http://d.hatena.ne.jp/rx7/20100625/p2
bashで変数内文字列の一部を置換するAdd Star
こんなやり方もあった。
$ STR="I have a pen."
$ echo ${STR/pen/notebook}
I have a notebook.
ん?なんだそれ?
>特殊な変数展開 - Miuran Business Systems
http://www.m-bsys.com/linux/variable_expansion
>## 変数からの簡易文字列編集
>パターンマッチングを使用した文字列編集も出来ます。ただ、一般的な正規表現が使えないため、私には使いづらいです…。単純な文字列パターンのときには有効です。「[0-9]」「[abc]」の等表現はOK、「*」は任意の文字列扱いになります。「任意の回数の繰り返し」は表現できません。
>
| 表現 | 説明 |
|----------------------------|----------------------------------------------------------------------------|
| ${変数名#パターン} | 変数の先頭がパターンマッチした場合、最短マッチ部分を削除した文字列を返す。 |
| ${変数名##パターン} | 変数の先頭がパターンマッチした場合、最長マッチ部分を削除した文字列を返す。 |
| ${変数名%パターン} | 変数の末尾がパターンマッチした場合、最短マッチ部分を削除した文字列を返す。 |
| ${変数名%%パターン} | 変数の末尾がパターンマッチした場合、最長マッチ部分を削除した文字列を返す。 |
| **${変数名/パターン/文字列}** | **最初にパターンマッチした部分を文字列で置換した文字列を返す。** |
| **${変数名//パターン/文字列}** | **パターンマッチしたすべての部分を文字列で置換した文字列を返す。** |
これだ!
正規表現は使えませんが、今回の場合はそんな高等なものは必要ないのでこれで十分です。
つまり
```bash:before
elements[$j]=$(echo "${elements[$j]}" | sed -e 's/\//./g')
が
elements[$j]=${elements[$j]//\//.}
こう書けるんですね。
結果
実行時間が30分から1分に短縮した
私「それなら待てるわ。」
結論
猫も杓子もsed
使うんじゃなくて、変数展開のことも思い出してあげてください。
それはそうとMSははようfork()
実装しろ
@yumetodo Win32/Win64 Subsystem は fork でない普通の CreateProcess の時点で既に遅いから 、fork が実装されたとしても factor 変わるだけで問題は解決しない気がする
— akinomyoga (@akinomyoga) 2017年1月14日
@yumetodo 隠し API ZwCreateProcess で fork ができるという話もある (詳しくは知らないけれど)。 https://t.co/WcSCY57K8q
— akinomyoga (@akinomyoga) 2017年1月14日
@yumetodo 間違えた。パイプしない場合を聞きたかった。例えばこんな比較。 pic.twitter.com/wQ5l4iuo9R
— 173210.go (@173210) 2017年1月15日
@173210 pic.twitter.com/tafOYztIo5
— yumetodo-C++erだけど化学科 (@yumetodo) 2017年1月15日
@yumetodo 決まりだな。MSYS環境でのパイプはクソ遅い。
— 173210.go (@173210) 2017年1月15日