Linuxスペースを含む文字列の置換


シェルスクリプトで文字列を書き換えて,足りない分はスペースで埋めるコードを書いたので,調べた内容やつまずいたところなどを備忘録として残しておく.

課題

背景

C言語の研修中の話.ソースコードを書くときに,ヘッダコメントを毎回書き換える必要があった.例えば

training1-1.c
/*###################################################*/
/*#           File      :  training1-1.c            #*/
/*#           Date      :  2019/10/22               #*/
/*###################################################*/

int main(void){
    printf("Hello World\n");
    return 0;
}

みたいな,かんじ.
特に,研修中なんて「training1-1.c」のようにファイル名がほとんど同じなので,ファイル作成→ファイル名変更を繰り返していると毎回打ち直すのが面倒くさくなってくる.

実現したいこと

  1. 自動でヘッダコメントを編集し,コメントアウトのスペースの数もキレイに揃える
  2. vimでソースコードを書いて保存したら,そのまま実行結果を出力してくれる

Linux環境でファイルの作成からコンパイルまでを行っていたので,ちょうど勉強中のシェルスクリプトの知識を使って作ってみた.

成果物

今回作ったヘッダコメントの編集,コンパイル,実行をしてくれるシェルスクリプト(zikko.sh)は,基になるテンプレート(template.c)と合わせて使うことになる.
実行方法とzikko.sh,template.cの中身は以下の通り.また,<ファイル名>は拡張子の前まででOK.

#実行方法
$ ./zikko.sh template <ファイル名>
zikko.sh
#!/bin/bash

template=$1
newFile=$2
date=$(date "+%Y/%m%d")
convertSymbolFile=$"FILE_NAME"
convertSymbolDate=$"YYYY/MM/DD"

#ファイルが存在しない場合は,ヘッダファイルを編集
if [ ! -e $newFile.c ]; then
    #スペースを調整してファイルのタイトルを変更
    if [ ${#newFile} -le ${#convertSymbolFile} ]; then
        diffOfStr=$((${#convertSymbolFile}-${#newFile}))
        numOfSpace=$(seq -s" " $((diffOfNum+1)) | tr -d '[:digit:]')
        newFileTitle=$newFile$numOfSpace
        sed -e "s:$convertSymbolFile:$newFileTitle:g" $template.c > $newFile.c
    #else
    #変換後文字列の長さ > FILE_NAME の場合の処理も書こうとしたが,時間の都合上断念
    #convertSymbolFileの文字列を長くすれば問題ない気がする
    fi
    #日付を変更
    sed -i -e "s:$convertSymbolDate:$date:g" $newFile.c
fi

#main関数が書かれている行から編集開始
mainFuncLow=$(grep SOURCE_CODE -n $newFile.c | cut -d ":" -f 1)
vim -c $mainFuncLow $newFile.c

#コンパイルと実行
gcc -o $newFile.x $newFile.c
./$newFile.x

template.c
/*###################################################*/
/*#           File      :  FILE_NAME                #*/
/*#           Date      :  YYYY/MM/DD               #*/
/*###################################################*/

int main(void){
    //SOURCE_CODE
    return 0;
}

テンプレートに変換前の文字列を追記して,zikko.shのファイルのタイトルを変える処理のところを少し弄れば,単純なヘッダコメントの追加には対応できるはず...
単純そうな作業の割には,コードが長い気もする.もっといい方法あったら教えてください.

つまずいたポイント

スペースの繰り返し

「Linux 文字列置換 繰り返し」とかで検索かけると,記号の繰り返しに関する記事はけっこうヒットするが,スペースを繰り返し挿入する記事はあまり見つけられなかった.見つけた中でseqコマンドの結果をパイプラインでtrコマンドに送る方法1を用いた方法が一番簡単そうだった.

つまずいたポイント1.sh
#diffOfNum=4

$ seq -s" " $((diffOfNum+1)) | tr -d '[:digit:]'
#->**** ※スペースの代わりに*で表示
  • printコマンドの引数でNUM(任意の値)を与えても,画面には(NUM-1)回しか表示されないので注意が必要.NUM回表示したいならば,引数に(NUM+1)を与える必要がある
  • スペースを繰り返し表示したい場合は,-sオプションの後を " " にする必要がある.記号の場合は,-sオプションの後を繰り返し表示したい記号を入れればOK.

文字列の結合

シェルスクリプトで文字列を結合するには,シェル変数を連続して書けばいいらしい2

つまずいたポイント2.sh
#newFile=file1 ※<ファイル名>
#numOfSpace=****

$ newFileTitle=$newFile$numOfSpace
#->file1****

sedコマンドの使い方

文字列の置換のためにsedコマンドよく使ったので,使い方や躓いたポイントを簡単にメモしておく.まず,実行方法は以下の通り.

$ sed [オプション] <スクリプト> <ファイル名>

<スクリプト>には,アドレスとコマンドを組み合わせた文字列が入る.よく使われるコマンドは,

コマンド 実行結果
s 文字列の置換
d 行の削除
p 行の表示

がある.今回は文字列の置換しかしていないので,sコマンドについてのみ書いておく.

変数展開

sコマンドを''(シングルクオート)で囲んで文字列の置換を行うと,sedコマンドの処理は通るがFILE_NAMEという文字列が置換されていないという問題が生じた.そのときのコードは以下の通り.

つまずいたポイント3.sh
#convertSymbolFile="FILE_NAME"
#newFileTitle=file1****
#template=template
#newFile=file1

$ sed -e 's/$convertSymbolFile/$newFileTitle/g' $template.c > $newFile.c
#->実行はされるがFILE_NAMEは置換されていない

調べてみると,置換できていない原因は,変数展開に失敗していたことが原因だった.この問題を解決するには,変数の部分だけ引用符を解除する必要があるらしい.3
そんなわけで,変数展開の方法を修正したコードがこちら.

つまずいたポイント3-2.sh
$ sed -e 's/'$convertSymbolFile'/'$newFileTitle'/g' $template.c > $newFile.c
#->sed: -e expression #1, char 16: sコマンドが終了していません

コードを修正したら,今度は別のエラーが出てしまった...

スペースを含む文字列の置換

エラー:「sコマンドが終了していません(unterminated `s' command)」が発生した原因は,"(ダブルクオート)と'(シングルクオート)で囲んだときのエスケープ処理の有無の違いの模様.'(シングルクオート)はbashによる展開等を行わずsedコマンドに文字列をそのまま渡してあげるイメージらしい4
ダブルクオートで囲って変数展開させるように修正5したコードは以下の通り.

つまずいたポイント3-3.sh
$ sed -e "s/$convertSymbolFile/$newFileTitle/g" $template.c > $newFile.c
#->FILE_NAMEの置換が上手く行われた

一応,fileの文字列からスペースを除いた状態で「つまずいたポイント3-2.sh」を実行してみたら問題なく実行できた.

日付の置換

コメントヘッダの作成日時を自動で編集するために,sedコマンドを使って日付の文字列の置換をしようとしたら,エラー:「sコマンドが未知です(unknown option to `s' command)」が起きてしまった.

つまずいたポイント3-4.sh
#newFile=file1
#date=2019/10/22
#convertSymbolDate=$"YYYY/MM/DD"

$ sed -i -e "s/$convertSymbolDate/$date/g" $newFile.c
#->sed: -e expression #1, char 34: unknown option to `s'

sedコマンドは、sの次に書いた文字が自動的に区切り文字として認識れるのが原因だった6.区切り文字に/を使っていたため,s/YYYY/MM/DD/2019/10/22/gとなってしまい,エラーを吐いたのだと考えられる.
区切り文字を修正したコードは,以下のようになる.

つまずいたポイント3-5.sh
$ sed -i -e "s:$convertSymbolDate:$date:g" $newFile.c
#->YYYY/MM/DDの置換が上手く行われた

検索文字の行番号だけを取得

vimを起動したときに自動的にmain関数のところにカーソルをもってくるために行番号をvimの-cオプションに渡したかったが,grepコマンドだけでは出力が「行番号 : <検索対象の文字列>」になってしまうという問題があった.
これは,grepコマンドの標準出力をパイプラインでcutコマンドに渡すことで解決できた7

つまずいたポイント4.sh
#newFile=file1

$ grep SOURCE_CODE -n $newFile.c | cut -d ":" -f 1
#->7

追記

これを使えばもっと簡単にできたかも?
参考8