VOOZH about

URL: https://qiita.com/yumetodo/items/3665e343c8063f56ab6a

⇱ sedなんか投げ捨てて変数展開で高速に文字列を置換しよう #Bash - Qiita


👁 Image
37

Go to list of users who liked

49

Share on X(Twitter)

Share on Facebook

Add to Hatena Bookmark

More than 1 year has passed since last update.

@yumetodoin👁 Image
Qiitadonユーザー会

sedなんか投げ捨てて変数展開で高速に文字列を置換しよう

37
Last updated at Posted at 2017-01-14

はじめに

普段は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で変数内文字列の一部を置換する

こんなやり方もあった。

$ 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、「*」は任意の文字列扱いになります。「任意の回数の繰り返し」は表現できません。

表現 説明
${変数名#パターン} 変数の先頭がパターンマッチした場合、最短マッチ部分を削除した文字列を返す。
${変数名##パターン} 変数の先頭がパターンマッチした場合、最長マッチ部分を削除した文字列を返す。
${変数名%パターン} 変数の末尾がパターンマッチした場合、最短マッチ部分を削除した文字列を返す。
${変数名%%パターン} 変数の末尾がパターンマッチした場合、最長マッチ部分を削除した文字列を返す。
${変数名/パターン/文字列} 最初にパターンマッチした部分を文字列で置換した文字列を返す。
${変数名//パターン/文字列} パターンマッチしたすべての部分を文字列で置換した文字列を返す。

これだ!

正規表現は使えませんが、今回の場合はそんな高等なものは必要ないのでこれで十分です。

つまり

before
 elements[$j]=$(echo "${elements[$j]}" | sed -e 's/\//./g')

after
 elements[$j]=${elements[$j]//\//.}

こう書けるんですね。

結果

実行時間が30分から1分に短縮した

私「それなら待てるわ。」

結論

猫も杓子もsed使うんじゃなくて、変数展開のことも思い出してあげてください。

@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日

License

CC BY 4.0

👁 CC-BY アイコン

37

Go to list of users who liked

49
2

Go to list of comments

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
37

Go to list of users who liked

49