1
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

ssh先で実行するコマンドのエスケープが面倒なときはbase64でencode/decodeするという選択肢

Last updated at Posted at 2019-05-30

2019-06-04 追記

いくつかコメントいただいたので、いろいろ試してみた内容を追記します

私がShellの仕様をきちんと理解せずに投稿していた部分もあり、勉強になりました

結論からいうと

  1. base64なんて使わなくてもssh先のサーバにコマンドは流せます
  2. cat encoded.txt | ssh HOSTNAME "$(base64 -d)""$(base64 -d)"の部分はssh先ではなくローカルでdecodeされます(リモートでされると勘違いしてました)

ただ、実際に私が開発している過程でbase64を使って、上記のShellのテクニックや複雑なエスケープ処理を正しく動作させるまでの手間を回避していたことも事実なので、コードの実例を交えて紹介しておきます

問題は、たんにssh先のホストにコマンドが流せればよいわけではなくNode.jsの文字列をsshコマンドを通してリモートホストで流す必要があったということです

Node.jsからsshを経由して、リモートサーバ上で複雑なShellSctiptを実行するいくつかの方法

以下のようなコマンドをNode.jsからsshを経由してリモートのサーバ上で実行したいとします

cat /etc/hosts \
|grep "^192" \
|awk '{print $2}' \
|sed -e "s/^target\([1-3]\)$/updated\1/g" \
> ~/tmp/results.hosts

/etc/hostsの中から192で始まる行を抜き出し、更にそのホスト名だけを抜き出します。そしてホスト名に'target'という文字列が含まれる場合は'updated'に置き換え、~/tmp/results.hostsに結果を出力します。出力先のファイルパスはリモートサーバ上のものです

そんな処理が必要になるような状況は想定しづらいですが開発しているソフトウェアの特性上、Shell上で正しく動作するコマンドは全てリモートサーバでも動作するようサポートしなければならないので、あえて複雑な例でソフトウェアの動作を確認しておいて損はないでしょう

案1: 普通にssh HOSTNAME COMMAND
Node.js
const exec=require('child_process').exec;


const cmd=String.raw`
  cat /etc/hosts \
  |grep "^192" \
  |awk '{print $2}' \
  |sed -e "s/^target\([1-3]\)$/updated\1/g" \
  > ~/tmp/results.hosts
`;

const ssh=`
  ssh target1 ${cmd}
`;

console.log(ssh);
exec(ssh, console.log);

`で囲まれている部分はテンプレート構文で、文字列中に変数や定数を埋め込むことができます。"'が含まれる文字列もこの構文を使えばエスケープなしで使えます

String.rawというのは文字列をそのまま扱ってくれるものです。これがないとテンプレート構文中の\1がうまく解釈されずに、SyntaxErrorとなります(Javascriptの細かい仕様は割愛)

そして実際このコードは終了しません。cmd定数の1行目が改行なのでssh target1のあとに改行が入ってしまい、ただtarget1にログインするだけで、コマンドは実行されません

\nを削除すればよいのですが、そうすると今度はあえて改行を入れているコマンドに対応できなくなります

案2: echo COMMAND | ssh HOSTNAME sh

このパターンだと今度は、コマンド内の'ないしは"をエスケープしなければなりません

Node.js
const exec=require('child_process').exec;


const cmd=String.raw`
  cat /etc/hosts \
  |grep "^192" \
  |awk '\''{print $2}'\'' \
  |sed -e "s/^target\([1-3]\)$/updated\\1/g" \
  > ~/tmp/results.hosts
`;

const ssh=`
  echo '${cmd}' | ssh target1 sh
`;

console.log(ssh);
exec(ssh, console.log);

テンプレート構文とShellの文字列が混在しているので、どうしてもどこかでエスケープが必要になります

案3: cat EOS ~ EOS を使う

この方法は問題なく動作しました

Node.js
const exec=require('child_process').exec;


const cmd=String.raw`
  cat /etc/hosts \
  |grep "^192" \
  |awk '{print $2}' \
  |sed -e "s/^target\([1-3]\)$/updated\1/g" \
  > ~/tmp/results.hosts
`;

const ssh=`
  cat << 'EOS' | ssh target1 sh
    ${cmd} 
EOS
`;

console.log(ssh);
exec(ssh, console.log);

ただし終わりのEOSはインデントすることができません。EOSの前に空白を入れると、コマンドとして認識されエラーになります。見栄えがよくないので、このことを忘れていて何かの拍子にインデントしてしまい「あれ?動かなくなった?」ということもあるかもしれません

案4: base64を使う

私なりに考えた結果、コマンドをbase64でエンコードしてしまえば、そもそもエスケープから解放されるのでは?との結論にいたり、ソフトウェア上でもShellコマンドは全てbase64でエンコードし、実行時にbase64 -dでデコードするよう実装することにしました

今のところ自分で利用している分には、問題なく動作しています

Node.js
const exec=require('child_process').exec;


const cmd=String.raw`
  cat /etc/hosts \
  |grep "^192" \
  |awk '{print $2}' \
  |sed -e "s/^target\([1-3]\)$/updated\1/g" \
  > ~/tmp/results.hosts
`;

const enc=Buffer.from(cmd)
  .toString('base64');

const ssh=`
  echo ${enc} | ssh target1 "$(base64 -d)"
`;

console.log(ssh);
exec(ssh, console.log);

以上です。何か良い方法があれば、ぜひ教えていただければと思います

ありがとうございました

※以降、追記前の記事


記事: https://qiita.com/mjusui/items/bce4566ffdb54ba7039a
ツール: Submarine - https://gitlab.com/mjusui/submarine

↑で紹介しているSubmarineというツールを作成しているときに、ひらめいた!

通常ssh先でコマンドを実行したい場合は以下のように書くと思います

ssh HOSTNAME COMMAND

ところが、実際にコマンドを実行してみるとコマンドの一部が、ssh元の方で評価されたり ¥ エスケープがうまくいかずにエラーになることがあります

そんなときにはssh先で実行したいコマンドをパイプで渡してやると、うまく行くことがあります

echo COMMAND | ssh HOSTNAME "$(cat -)"

しかし、それでも何らかの理由で失敗する場合はbase64でエンコードして、ssh先でデコードしてあげると、エスケープを気にせず、生のコマンドをssh先で実行できます

### エンコード
echo command.txt | base64 > encoded.txt

###  実行
cat encoded.txt | ssh HOSTNAME "$(base64 -d)"
1
2
2

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
1
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?