UNDO

今回はシェルスクリプトのヒアドキュメントの挙動について。
変数を使用した際に、コマンド結果をパイプで渡す際に注意すべきことを備忘録として書いておく。

概要


Oracle DBから情報を取る際にsqlplusを使用する人はシェルを書くこともあると思う。 よくあるのが、sqlplusコマンドにヒアドキュメントを使用してSQLを渡す方法。 この際、sqlplusコマンドに渡したSQLの実行結果に対してさらにパイプで別コマンドに渡して加工したい場合、ヒアドキュメントの開始のマークの直後にスペースを空けるとエラーになる。マークの直後にパイプしないといけないということ。 しかもこれ、変数を使用した場合のみエラーになるので気をつけたい。

環境


  • Linux : Red Hat Enterprise Linux Server release 6.5 (Santiago)
  • oracle 12c ( Single Instance )

ヒアドキュメントを用いた情報取得


DBへの接続方法の一つにサーバーからsqlplusコマンドを実行する方法がある。
この場合、このsqlplusコマンドにSQLをヒアドキュメントで渡すことでシェル越しにSQLを実行し、その結果を受け取ることができる。

こんな感じ。

[oracle@test1 Scripts]$ sqlplus -s / as sysdba <<'EOF'
> set heading off
> set pagesize 0
> set feedback off
> select thread# from v$instance;
> exit;
> EOF
	 1
[oracle@test1 Scripts]$

さて、分かりにくいが、上記の結果である’1’の前にはスペースが入っている。
そのため、この実行結果から数字だけ取り出したい場合、たとえば以下のようにパイプで別コマンドに渡す。

[oracle@test1 Scripts]$ sqlplus -s / as sysdba <<'EOF' | awk '{ print $1 }'
> set heading off
> set pagesize 0
> set feedback off
> select thread# from v$instance;
> exit;
> EOF
1
[oracle@test1 Scripts]$

この結果を変数に入れる場合は以下のようにする。

[oracle@test1 Scripts]$ THREAD=$( sqlplus -s / as sysdba <<'EOF' | awk '{ print $1 }'
> set heading off
> set pagesize 0
> set feedback off
> select thread# from v$instance;
> exit;
> EOF
> )
[oracle@test1 Scripts]$ echo $THREAD
1

このように、取得した情報を変数に入れて、それを使ってごにょごにょとスクリプトを作ったりすることは多いと思う。

ヒアドキュメント、パイプ、変数の組み合わせにハマる


シェルスクリプトにする時は、丁寧に書く場合、使用するコマンドは基本最初に絶対パスで宣言して、その変数を用いて書いていくと思うがそこでハマった。

うまくいく例

[oracle@test1 Scripts]$ THREAD=$( sqlplus -s / as sysdba <<'EOF' | awk '{ print $1 }'
> set heading off
> set pagesize 0
> set feedback off
> select thread# from v$instance;
> exit;
> EOF
> )
[oracle@test1 Scripts]$ echo $THREAD
1

うまくいかない例

上記のシェルの仕上げにsqlplusコマンドを変数に置き換えたところ、シェルが終了しなくなった。

[oracle@test1 Scripts]$ SQLPLUS=$ORACLE_HOME/bin/sqlplus
[oracle@test1 Scripts]$ THREAD=$( ${SQLPLUS} -s / as sysdba <<'EOF' | awk '{ print $1 }'
> set heading off
> set pagesize 0
> set feedback off
> select thread# from v$instance;
> exit;
> EOF
> )
>
>

原因

当初は、$()の中でヒアドキュメント用いたり、パイプを使う際は$()内で実行するコマンドは変数で使用できないのかと割り切ろうかと思ったがやはり解せず、試行錯誤をした結果、ヒアドキュメントとパイプの間にスペースがあるのが原因であることが分かった。

‘EOF’の直後にパイプ|を書いたところ、動作した。

[oracle@test1 Scripts]$ SQLPLUS=$ORACLE_HOME/bin/sqlplus
[oracle@test1 Scripts]$ THREAD=$( ${SQLPLUS} -s / as sysdba <<'EOF'| awk '{ print $1 }'
> set heading off
> set pagesize 0
> set feedback off
> select thread# from v$instance;
> exit;
> EOF
> )
[oracle@test1 Scripts]$ echo $THREAD
1
[oracle@test1 Scripts]$

最後に


この現象がメジャーなのかどうか分からずググったがあまり同じような経験にぶちあたった人がいなかったので、内容的には正直かなりしょぼいが備忘録として書いた。でもパイプに渡す時にスペース入れる人結構入れると思うし、変数じゃないと普通にスペースあってもいいし、うーん、よく分からん。あれ、変数を用いると変数展開が行われるからそれが関係しているのか。にしても正式なドキュメントをヒアリングしたいもの。ヒアドキュメントだけに。