Linux

Linux sudo で(変数展開を含む)for ループを実行する

こちらの記事でサラッと流してましたが、sudo コマンドの中でfor を実行した際エラーに遭遇したのでメモっておきます。

スポンサーリンク

事象

以下は、homeディレクトリ配下の各ユーザーディレクトリ分ループしてssh鍵をコピーするコマンドになります。

普通にカレントシェルでforを実行する分には正常に動くコマンドです。

$ sudo sh -c "for i in `ls -1 /home/`;do  cp --parents /home/$i/.ssh/id_rsa .  ; done"
sh: -c: 行 1: 予期しないトークン `ec2-user-2' 周辺に構文エラーがあります
sh: -c: 行 1: `ec2-user-2'
$

原因

デバッグのため -xv オプションを付けて実行してみます。

## -v
##  Print shell input lines as they are read. (シェルの行を実行時にそのまま表示する
## -x
##  Print commands and their arguments as they are executed. (シェルの行を展開した上で表示する
$
$ sudo sh -xvc "for i in `ls -1 /home/`;do  cp --parents /home/$i/.ssh/id_rsa .  ; done"
for i in ec2-user
ec2-user-2
sh: -c: 行 1: 予期しないトークン `ec2-user-2' 周辺に構文エラーがあります
sh: -c: 行 1: `ec2-user-2'
$

どうやら for の引数となっている ls -1 のコマンドが実行されてしまった上で、その結果がsh -c に渡されているようです。

↑を見る限り、ec2-user のケツで改行されており本来のfor構文であれば改行のあと do が入らなきゃいけない部分に ec2-user-2 という文字列がはいってしまい構文エラーとなっている模様。

対策

sh -c に渡す文字列をダブルクォーテーションではなくシングルクォーテーションで囲みます

これによりfor部分の変数の展開を防ぐことができ、以下のように意図どおりに動作できました。

$ sudo sh -vxc 'for i in `ls -1 /home/`;do  cp --parents /home/$i/.ssh/id_rsa .  ; done'
for i in `ls -1 /home/`;do  cp --parents /home/$i/.ssh/id_rsa .  ; done
ls -1 /home/
++ ls -1 /home/
+ for i in '`ls -1 /home/`'
+ cp --parents /home/ec2-user/.ssh/id_rsa .
+ for i in '`ls -1 /home/`'
+ cp --parents /home/ec2-user-2/.ssh/id_rsa .
+ for i in '`ls -1 /home/`'
+ cp --parents /home/ec2-user-3/.ssh/id_rsa .
$

Tips

突き詰めると、この事象は sudo というより sh -c の挙動になります。
以下の例だとわかりやすいかもしれません。

shに渡す際、囲む文字がダブルクォーテーションであると、$a がまず展開された上で sh に渡されます。 カレントシェルでは $a には何も設定していないので $a の展開結果は空のまま、ただのecho が実行されます。

$ sh -xvc "a=hoge ; echo $a"
a=hoge ; echo 
+ a=hoge
+ echo
$


シングルクォーテーションであれば、$a が$a という文字のまま sh -c に渡されるので $aに hoge が設定され、 echo hoge が実行されます。

$ sh -xvc 'a=hoge ; echo $a'
a=hoge ; echo $a
+ a=hoge
+ echo hoge
hoge
$

以上です〜ノシ

参考

(´・ω・`)ゞアリガトゴザイマス.。.・゚

forループでsudoを使う