

シェルスクリプト初心者で勉強中です。よろしくお願いします。

下記のようなコンフィグファイルがあって、

[code]

    valueA="aaaaaaa"
    valueB="[@$]" 
    
[/code]

上記のvalueBの値の  
`"[@$]"` の後に  
`"[@$]" '/xxx/' '/xxx/' '/xxx/' ........`  
という様にスペースで区切りながら任意のディレクトリパスを追記していける  
シェルスクリプトを作りたいと思っています。

include.lstというリストファイルの中に、`/xxx/`の部分のリストを作り  
それをwhile文で読込ませて実行しようとしましたが、  
sedが思ったように動きませんでした。

include.lstの中身の例です。

[code]

    /home/aaa/
    /usr/bbb/
    .
    .
    .
    
[/code]

失敗例です。  
<失敗例1>

[code]

    while read line 
    do
     sed -i -e "/valueB/s/$/ $line/g"
    done < include.lst
    
[/code]

==============================================  
<失敗例2>

[code]

    while read line 
    do
     sed -i -e "|valueB|s|$| $line|g"
    done < include.lst
    
[/code]

==============================================

これが`$line`のような変数でなければsed自体は成功します。  
例）

[code]

    sed -i -e "/valueB/s/$/ \'\/var\/\'/g"
    
[/code]

また、

[code]

    sed -i -e "s|xxx| $line|g"
    
[/code]

の様に先に検索文字列を入れなければ、これも成功します。

この様な場合、どうしたらいいかご存知の方がいらっしゃいましたらご教示頂けないでしょうか。  
別にwhile文やsedにこだわっているわけではありません。  
よろしくお願いします。


CC BY-SA 3.0
2017-09-11T03:21:03.897
37861
2017-10-14T03:23:10.683
2017-09-11T03:48:50.713
13199
25323
question
shellscript sh
シェルスクリプトで任意の行末に追記したい


### test.sh

[code]

    #!/bin/sh
    
    convert_include_list() {
        while read -r line
        do
            printf "'%s' " "$line"
        done
        echo
    }
    
    append_str=$(
        cat include.lst |
            convert_include_list |
            sed 's/ $//' )
    
    cat config.txt |
        append_str="${append_str}" awk '
            { if ($0 ~ /^valueB/)
                 print $0 " " ENVIRON["append_str"]
              else
                 print $0
            }
        '
    
[/code]

### 実行結果例(標準出力に出てきます)

[code]

    valueA="aaaaaaa"
    valueB="[@$]" '/home/aaa/' '/usr/bbb/' '/foo/ccc/'
    
[/code]

自分でしたら、上記のようにすると思います。ポイントは:

  * シェルスクリプトの処理は基本行指向なので、読み込んだ際に最終的に追記したい行の内容を、 include.lst から読み込んだ際に作ってしまう
  * sed では文字列を変数として扱うことが難しい（制御文字と被る）ので、 awk を用いる
  * awk に、シェルの変数を渡す際の一番安定した方法は、 上記のように ENVIRON 経由で渡すこと。 (参考: <https://stackoverflow.com/a/26779845/3090068> )

スクリプトを上から解説すると:

  * convert_include_list: 標準出力から include.lst の内容を読み取って、最終的に追記したい内容を標準出力に出力する。 while で実現。
  * append_str: コマンド置換でもって、 include.lst の内容を convert_include_list して、ついでに末尾の空白を削除したうえで、この変数に保持する。
  * 最後の awk 周り: awk コマンドに対して append_str 環境変数として、実行を行う。その際にダブルクォートで囲む (`append_str="${append_str}"`)ことで、変数をそのまま投入できる。 awk の中で、行が正規表現にマッチすれば、 `ENVIRON` 変数で `append_str` を取り出し、それを末尾に追記する。

上記スクリプトは結果が標準出力に出てくるので、既存のファイルを置き換える場合には、別の場所にリダイレクトでファイル作成してから、 mv します。


CC BY-SA 3.0
2017-09-11T07:26:53.837
37868
2017-09-11T07:26:53.837
754
37861
answer


パスの両端は `'` で囲むとします。  
ただし、パス内部に `'` が含まれる場合、エスケープを必要とするため、今回はシェルのエスケープを想定して `'"'"'` に置換します。

[code]

    # include.lst
    /home/aaa/
    /usr/bbb/
    /tmp/'a/
    
    ↓
    
    # config.txt
    valueB="[@$]" '/home/aaa/' '/usr/bbb/' '/tmp/'"'"'a/'
    
[/code]

全体のスクリプトは下記となります。

[code]

    $ qq="'"
    $ value_b_paths=$(sed '-e :loop;${;s/'$qq'/'$qq'"'$qq'"'$qq'/g;s/^/'$qq'/;s/$/'$qq'/;s/\n/'$qq' '$qq'/g;};N;bloop' include.lst)
    $ sed -i /valueB/a\\"$value_b_paths" config.txt && sed -i '-e /valueB/{;N;s/\n/ /;}' config.txt
    
[/code]

二行目で `valueB` の終端に追記する文字列 `value_b_paths` を作ります。  
`include.lst` を一気に全行読み込み、引用符を置換していきます。

三行目の前段で `valueB` の次行にパスを追記してファイル更新し、後段で一行にまとめて再度ファイル更新します。  
特殊文字が入ったパターンを適切にエスケープする手間を省くためです。

二行目はエスケープが入り組んでいるため複雑に見えますが、解きほぐすと単純な sed のスクリプトになります。

[code]

    :loop
    ${
      s/'/'"'"'/g
      s/^/'/
      s/$/'/
      s/\n/' '/g
    }
    N
    bloop
    
[/code]


CC BY-SA 3.0
2017-09-11T12:14:47.683
37873
2017-09-11T12:14:47.683
8431
37861
answer


$line がシェルによって展開されてから sed に渡されますので、$line に / が含まれていると unknown option to 's'
となってしまいます。ご自身でほとんど正解に辿り着いていますが、sed -i -e "/valueB/s|$| $line|g" config.txt
になります。とは言え、$line に文字 | が含まれている場合にはやはりエラーになりますが（可能性は低いでしょうけれども）


CC BY-SA 3.0
2017-09-11T13:17:58.823
37874
2017-09-11T13:17:58.823
25323
37861
answer