※当サイトの記事には、広告・プロモーションが含まれます。

シェルスクリプトの変数にOracle DatabaseのSELECTの結果(複数行)を格納したいのだが...

www.itmedia.co.jp

pcireadycloud.com

⇧ まぁ、高いと言われる日本のクレジット決済の手数料と比較して高くないと言うのには意味が無いとは思うけど...

シェルスクリプトの変数にOracle DatabaseのSELECTの結果(複数行)を格納したいのだが...

ネットの情報を見てると、

libproc.com

totech.hateblo.jp

oha-yo.com

hnamaizawa.hatenadiary.org

⇧ 何故か、皆々様、SELECTの結果が単一行のケースしか紹介してくれていない...

分からんけど、バッチ処理とか想定するんだったら、SELECTの結果が複数行になるってことはあるあるだと思うんだが...

Oracle DatabaseでSELECTの結果が複数行をシェルスクリプトの変数に格納するケースは、ネット上を探した限りは見つからなんだ...

単純に、

everything-you-do-is-practice.blogspot.com

⇧ 出力してるケースはあるっぽいけど...

Oracle Database以外では、SELECTの結果が複数行をシェルスクリプトの変数に格納するって実装がネット上で情報が見つかるのだけど、Oracle Databaseって枯れた技術の割には、情報が少ないんよね...

致し方ないので、自力で実装することになりました。

Oracle Databaseの場合は、

qiita.com

CSV出力の場合、SQLPLUS -MARKUP CSV ONを使用して、出力をCSV形式で生成します。DELIMITERオプションを使用して、デリミタ文字を指定できます。QUOTE OFFを使用して、引用符なしでテキストを出力することもできます。

Oracle Database のデータを SQL*Plus で CSV 出力する - Qiita

⇧ オプションを指定して、SELECTの結果を変形してあげる必要があるらしい。

そもそもとして、

future-architect.github.io

さて、ここで少々(個人的に)致命的な問題があるのですが、bashの配列・連想配列は1次元しか扱えないという点です。
他言語だとしれっと使えてしまうので、「えっ、なんで!?」となりがちですが、多次元配列「的な」ものを作ることでそれっぽい処理は可能です。

declare使ってBashで配列と連想配列 | フューチャー技術ブログ

シェルスクリプトは1次元配列しか扱えないらしい...

というわけで、SELECTの結果が複数行の場合に、シェルスクリプトの変数に格納するのを試してみました。

環境は、

ts0818.hatenablog.com

⇧ 上記の記事の時と同じで、「WSL 2(Windows SubSystem for Linux 2)」にインストールした「Oracle Linux 8.7」、「Oracle Database 23c(Free-Developer Release)」を利用しています。

シェルスクリプトのファイルは以下のような感じになりました。

#!/bin/bash

# Oracle Databaseの接続情報
DB_USER=dev_web
DB_PASS=pass
DB_HOST=10.255.255.16
DB_PORT=1521
DB_SID=FREEPDB1

readonly input_code="$1"

echo "'${input_code}'${input_code}"

# 件数を取得
SELECT_COUNT=$(sqlplus -s ${DB_USER}/${DB_PASS}@${DB_HOST}:${DB_PORT}/${DB_SID} << EOF
  SET HEADING OFF
  SELECT COUNT(*)
  FROM W_M_FACILITY
  WHERE FACILITY_CODE='${input_code}';
  exit;
EOF
)

# 検索結果が1件かどうかチェック
if [ "${SELECT_COUNT}" -ne 1 ]; then
  echo "取得件数:${SELECT_COUNT}"
  echo "データが不正です。"
  exit 1
fi

SELECT_ROW=$(sqlplus -s -M 'CSV ON' ${DB_USER}/${DB_PASS}@${DB_HOST}:${DB_PORT}/${DB_SID} << EOF
  SELECT 
    FACILITY_CODE
    ,FACILITY_NAME
  FROM W_M_FACILITY
  WHERE ROWNUM <= 10
  ORDER BY FACILITY_CODE;
  exit;
EOF
)

# SELECTの結果を一時的なファイルに書き込む
echo "${SELECT_ROW}" > temp_file

# 行数カウント用
row_index=0

# 作業用の配列
work_arr=()

# カラム名の配列
column_arr=()

# 2次元配列(1行分ずつを格納用)
declare -A two_dimention_arr

two_dimention_arr_index=0

# 【start】SELECTの結果を1行ずつループ処理 
while read -a arr; do

  echo '【start】'"${row_index}"'行目の処理開始。'
  echo "${row_index}行目の"'${arr}'"${arr}"
  
  # 空行の場合
  if [ -z "${arr}" ]; then
    echo "${row_index}"'行目は空でした。${arr}'"${arr}"
    echo "次の行へ処理をスキップします。"
    echo '【end】'"${row_index}"'行目の処理終了。'
    # 行数をインクリメント
    row_index=$((row_index + 1))
    continue
  fi

  # 1行分をカンマで分割
  work_arr=(${arr//,/ })
  echo "${row_index}行目の"'${work_arr[@]}'"${work_arr[@]}"
  
  # 【start】1行に含まれるカラムの数だけループ処理
  for ((work_arr_index=0; ${#work_arr[*]}>work_arr_index; work_arr_index++)); do
      
      # ヘッダー行(カラム名)の場合
      if [ "${row_index}" -eq 1 ]; then
      
        # カラム名のリストを作成
        column_arr=("${work_arr[@]}")
        echo '${column_arr[@]}'"${column_arr[@]}"
        break
      fi
      
      # ヘッダー行以外の場合
      # カラム名を表示
      echo '${column_arr[${work_arr_index}]}'"${column_arr[${work_arr_index}]}"
      # カラム名の要素に格納される値を表示
      echo '${work_arr[${work_arr_index}]}'"${work_arr[${work_arr_index}]}"
      
      # 疑似的な2次元配列に値を格納
      two_dimention_arr[${two_dimention_arr_index},${column_arr[${work_arr_index}]}]=${work_arr[${work_arr_index}]}

      echo '${two_dimention_arr['"${two_dimention_arr_index}, ${column_arr[${work_arr_index}]}"']}='"${two_dimention_arr[${two_dimention_arr_index}, ${column_arr[${work_arr_index}]}]}"

      # 最後の要素の場合
      if [ $((work_arr_index + 1)) -eq ${#work_arr[*]} ]; then
      
        # 2次元配列のインデックスをインクリメント
        two_dimention_arr_index=$((two_dimention_arr_index + 1))
      
      fi

  # 【end】1行に含まれるカラムの数だけループ処理
  done

  echo '【end】'"${row_index}"'行目の処理終了。'
  # 次の行数へインクリメント
  row_index=$((row_index + 1))

# 【end】SELECTの結果を1行ずつループ処理 
done < temp_file

# 一時的なファイルを削除
rm temp_file

# 結果出力
echo '結果${two_dimention_arr[@]}'"${two_dimention_arr[@]}"
echo '${two_dimention_arr_index}'"${two_dimention_arr_index}"
# 行数分ループ処理
for ((i=0; i<${two_dimention_arr_index}; i++)); do

  echo '2次元配列の'"$((i + 1))"'行目'
  
  # カラムの数だけ繰り返し
  for column in "${column_arr[@]}"; do
    echo -n '${two_dimention_arr}['"${i}"','"${column}"']='
    echo ${two_dimention_arr[${i},${column}]}
  done
done

上記のファイルを、WinSCPなどで、「WSL 2(Windows SubSystem for Linux 2)」の「Oracle Linux 8.7」の$HOMEディレクトリにに配置します。

で、コマンドプロンプトなどで「WSL 2(Windows SubSystem for Linux 2)」の「Oracle Linux 8.7」にログインし、配置したシェルスクリプトのファイルを実行すると、

コマンドプロンプト上だと、文字の豆腐化で全く読解できないけど、サクラエディタなどのテキストエディタに貼り付けると、文字化けはしていないのを確認できます。

'0000000000001'0000000000001
【start】0行目の処理開始。
0行目の${arr}
0行目は空でした。${arr}
次の行へ処理をスキップします。
【end】0行目の処理終了。
【start】1行目の処理開始。
1行目の${arr}"FACILITY_CODE","FACILITY_NAME"
1行目の${work_arr[@]}"FACILITY_CODE" "FACILITY_NAME"
${column_arr[@]}"FACILITY_CODE" "FACILITY_NAME"
【end】1行目の処理終了。
【start】2行目の処理開始。
2行目の${arr}"0000000000474","伊那プリンスホテル"
2行目の${work_arr[@]}"0000000000474" "伊那プリンスホテル"
${column_arr[${work_arr_index}]}"FACILITY_CODE"
${work_arr[${work_arr_index}]}"0000000000474"
${two_dimention_arr[0, "FACILITY_CODE"]}=
${column_arr[${work_arr_index}]}"FACILITY_NAME"
${work_arr[${work_arr_index}]}"伊那プリンスホテル"
${two_dimention_arr[0, "FACILITY_NAME"]}=
【end】2行目の処理終了。
【start】3行目の処理開始。
3行目の${arr}"0000000000475","白馬東急ホテル"
3行目の${work_arr[@]}"0000000000475" "白馬東急ホテル"
${column_arr[${work_arr_index}]}"FACILITY_CODE"
${work_arr[${work_arr_index}]}"0000000000475"
${two_dimention_arr[1, "FACILITY_CODE"]}=
${column_arr[${work_arr_index}]}"FACILITY_NAME"
${work_arr[${work_arr_index}]}"白馬東急ホテル"
${two_dimention_arr[1, "FACILITY_NAME"]}=
【end】3行目の処理終了。
【start】4行目の処理開始。
4行目の${arr}"0000000000476","ロ-ゼンハイム白馬"
4行目の${work_arr[@]}"0000000000476" "ロ-ゼンハイム白馬"
${column_arr[${work_arr_index}]}"FACILITY_CODE"
${work_arr[${work_arr_index}]}"0000000000476"
${two_dimention_arr[2, "FACILITY_CODE"]}=
${column_arr[${work_arr_index}]}"FACILITY_NAME"
${work_arr[${work_arr_index}]}"ロ-ゼンハイム白馬"
${two_dimention_arr[2, "FACILITY_NAME"]}=
【end】4行目の処理終了。
【start】5行目の処理開始。
5行目の${arr}"0000000000477","白馬樅の木ホテル"
5行目の${work_arr[@]}"0000000000477" "白馬樅の木ホテル"
${column_arr[${work_arr_index}]}"FACILITY_CODE"
${work_arr[${work_arr_index}]}"0000000000477"
${two_dimention_arr[3, "FACILITY_CODE"]}=
${column_arr[${work_arr_index}]}"FACILITY_NAME"
${work_arr[${work_arr_index}]}"白馬樅の木ホテル"
${two_dimention_arr[3, "FACILITY_NAME"]}=
【end】5行目の処理終了。
【start】6行目の処理開始。
6行目の${arr}"0000000000478","白馬スプリングスホテル"
6行目の${work_arr[@]}"0000000000478" "白馬スプリングスホテル"
${column_arr[${work_arr_index}]}"FACILITY_CODE"
${work_arr[${work_arr_index}]}"0000000000478"
${two_dimention_arr[4, "FACILITY_CODE"]}=
${column_arr[${work_arr_index}]}"FACILITY_NAME"
${work_arr[${work_arr_index}]}"白馬スプリングスホテル"
${two_dimention_arr[4, "FACILITY_NAME"]}=
【end】6行目の処理終了。
【start】7行目の処理開始。
7行目の${arr}"0000000000479","白馬ロイヤルホテル"
7行目の${work_arr[@]}"0000000000479" "白馬ロイヤルホテル"
${column_arr[${work_arr_index}]}"FACILITY_CODE"
${work_arr[${work_arr_index}]}"0000000000479"
${two_dimention_arr[5, "FACILITY_CODE"]}=
${column_arr[${work_arr_index}]}"FACILITY_NAME"
${work_arr[${work_arr_index}]}"白馬ロイヤルホテル"
${two_dimention_arr[5, "FACILITY_NAME"]}=
【end】7行目の処理終了。
【start】8行目の処理開始。
8行目の${arr}"0000000000480","志賀高原プリンスホテル東館"
8行目の${work_arr[@]}"0000000000480" "志賀高原プリンスホテル東館"
${column_arr[${work_arr_index}]}"FACILITY_CODE"
${work_arr[${work_arr_index}]}"0000000000480"
${two_dimention_arr[6, "FACILITY_CODE"]}=
${column_arr[${work_arr_index}]}"FACILITY_NAME"
${work_arr[${work_arr_index}]}"志賀高原プリンスホテル東館"
${two_dimention_arr[6, "FACILITY_NAME"]}=
【end】8行目の処理終了。
【start】9行目の処理開始。
9行目の${arr}"0000000000481","志賀観光ホテル"
9行目の${work_arr[@]}"0000000000481" "志賀観光ホテル"
${column_arr[${work_arr_index}]}"FACILITY_CODE"
${work_arr[${work_arr_index}]}"0000000000481"
${two_dimention_arr[7, "FACILITY_CODE"]}=
${column_arr[${work_arr_index}]}"FACILITY_NAME"
${work_arr[${work_arr_index}]}"志賀観光ホテル"
${two_dimention_arr[7, "FACILITY_NAME"]}=
【end】9行目の処理終了。
【start】10行目の処理開始。
10行目の${arr}"0000000000482","ホテルタガワ"
10行目の${work_arr[@]}"0000000000482" "ホテルタガワ"
${column_arr[${work_arr_index}]}"FACILITY_CODE"
${work_arr[${work_arr_index}]}"0000000000482"
${two_dimention_arr[8, "FACILITY_CODE"]}=
${column_arr[${work_arr_index}]}"FACILITY_NAME"
${work_arr[${work_arr_index}]}"ホテルタガワ"
${two_dimention_arr[8, "FACILITY_NAME"]}=
【end】10行目の処理終了。
【start】11行目の処理開始。
11行目の${arr}"0000000000483","志賀高原プリンスホテル西館"
11行目の${work_arr[@]}"0000000000483" "志賀高原プリンスホテル西館"
${column_arr[${work_arr_index}]}"FACILITY_CODE"
${work_arr[${work_arr_index}]}"0000000000483"
${two_dimention_arr[9, "FACILITY_CODE"]}=
${column_arr[${work_arr_index}]}"FACILITY_NAME"
${work_arr[${work_arr_index}]}"志賀高原プリンスホテル西館"
${two_dimention_arr[9, "FACILITY_NAME"]}=
【end】11行目の処理終了。
【start】12行目の処理開始。
12行目の${arr}
12行目は空でした。${arr}
次の行へ処理をスキップします。
【end】12行目の処理終了。
【start】13行目の処理開始。
13行目の${arr}10行が選択されました。
13行目の${work_arr[@]}10行が選択されました。
${column_arr[${work_arr_index}]}"FACILITY_CODE"
${work_arr[${work_arr_index}]}10行が選択されました。
${two_dimention_arr[10, "FACILITY_CODE"]}=
【end】13行目の処理終了。
結果${two_dimention_arr[@]}"0000000000481" "ロ-ゼンハイム白馬" "志賀高原プリンスホテル西館" "白馬スプリングスホテル" "ホテルタガワ" "0000000000479" "0000000000483" "0000000000476" "0000000000475" "志賀高原プリン スホテル東館" "伊那プリンスホテル" "0000000000478" "0000000000474" "0000000000477" "0000000000480" "白馬ロイヤルホテル" "志賀観光ホテル" 10行 が選択されました。 "白馬東急ホテル" "白馬樅の木ホテル" "0000000000482"
${two_dimention_arr_index}11
2次元配列の1行目
${two_dimention_arr}[0,"FACILITY_CODE"]="0000000000474"
${two_dimention_arr}[0,"FACILITY_NAME"]="伊那プリンスホテル"
2次元配列の2行目
${two_dimention_arr}[1,"FACILITY_CODE"]="0000000000475"
${two_dimention_arr}[1,"FACILITY_NAME"]="白馬東急ホテル"
2次元配列の3行目
${two_dimention_arr}[2,"FACILITY_CODE"]="0000000000476"
${two_dimention_arr}[2,"FACILITY_NAME"]="ロ-ゼンハイム白馬"
2次元配列の4行目
${two_dimention_arr}[3,"FACILITY_CODE"]="0000000000477"
${two_dimention_arr}[3,"FACILITY_NAME"]="白馬樅の木ホテル"
2次元配列の5行目
${two_dimention_arr}[4,"FACILITY_CODE"]="0000000000478"
${two_dimention_arr}[4,"FACILITY_NAME"]="白馬スプリングスホテル"
2次元配列の6行目
${two_dimention_arr}[5,"FACILITY_CODE"]="0000000000479"
${two_dimention_arr}[5,"FACILITY_NAME"]="白馬ロイヤルホテル"
2次元配列の7行目
${two_dimention_arr}[6,"FACILITY_CODE"]="0000000000480"
${two_dimention_arr}[6,"FACILITY_NAME"]="志賀高原プリンスホテル東館"
2次元配列の8行目
${two_dimention_arr}[7,"FACILITY_CODE"]="0000000000481"
${two_dimention_arr}[7,"FACILITY_NAME"]="志賀観光ホテル"
2次元配列の9行目
${two_dimention_arr}[8,"FACILITY_CODE"]="0000000000482"
${two_dimention_arr}[8,"FACILITY_NAME"]="ホテルタガワ"
2次元配列の10行目
${two_dimention_arr}[9,"FACILITY_CODE"]="0000000000483"
${two_dimention_arr}[9,"FACILITY_NAME"]="志賀高原プリンスホテル西館"
2次元配列の11行目
${two_dimention_arr}[10,"FACILITY_CODE"]=10行が選択されました。
${two_dimention_arr}[10,"FACILITY_NAME"]=    

という結果になり、SELECTの結果が複数行の場合について、シェルスクリプトの変数に格納できてるようです。

どうやら、

echo '${two_dimention_arr['"${two_dimention_arr_index}, ${column_arr[${work_arr_index}]}"']}='"${two_dimention_arr[${two_dimention_arr_index}, ${column_arr[${work_arr_index}]}]}"

カンマの前に半角スペース入ってると値が上手く取り出せないっぽい...echoで表示されてないし...

なので、

${two_dimention_arr[${two_dimention_arr_index}, ${column_arr[${work_arr_index}]}]}
${two_dimention_arr[${two_dimention_arr_index},${column_arr[${work_arr_index}]}]}

って感じで、半角スペースを入れないように注意ですかね...

ただ、「10行が選択されました。」とか余計な文言も格納されちゃってますが...

あと、値にカンマが含まれてる場合とか上手くいかないような気もするし、そもそも、SQLの結果をシェルスクリプトの変数に格納することは想定されていないんかね?

シェルスクリプト、便利な気がしないんだが...

毎度モヤモヤ感が半端ない...

今回はこのへんで。