[シェルスクルリプト] RedshiftのCOPYで使用するマニフェストを作成する。

2016.10.09

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

はじめに

S3に配置した大量のCSVをRedshiftにロードしなければならず、マニフェストに記述すればCOPY文で楽々ロードが可能になります。
そこで、ファイル一覧からマニフェストを作成するスクリプトを作る事になったので、調べた事を記事にします。
AWS CLIインストール済みが前提です。

環境

Mac OSX 10.10.5 Yosemite
Atom 1.10.2
AWS CLI 1.10.1
Python 2.7.11
Darwin 14.5.0
botocore 1.3.23

Vimではないエディタを使う

理由1、操作が普通

Atomじゃなくても良いですが、初心者にVimの操作は面倒な感じがします。
Vimmerと呼ばれる方々もいらっしゃるそうですが...

理由2、権限の設定が不要

エディタから「〜.sh」で保存するだけです。
Atomでは事前にカラースキーマを設定していれば、「〜.sh」で保存した直後から適応されます。
ちなみにスクリプトをコマンドで作成する場合、下記の様にしないといけません。

$ vi test.sh
$ chmod 755 test.sh

権限を変更しないと実行できない様です。

目的

下記の実行でRedshiftのCOPYで使用するマニフェストファイルをカレントディレクトリに作成する。
スクリプト名は「create_manifest.sh」。

$ sh create_manifest.sh バケット名/オブジェクト名 検索文字列 プロファイル名 作成するファイル名

cliだけ実行してみる

$ aws s3 ls s3://バケット名 --recursive --profile プロファイル名
2016-09-28 17:02:15          0 test/
2016-09-28 18:34:42        186 test/test.manifest
2016-09-28 18:33:02          0 test/test_sub/
2016-09-28 18:33:15         62 test/test_sub/tablea_2.csv
2016-09-28 18:03:34         53 test/tablea_1.csv
2016-09-28 17:02:33         74 test/tableb.csv

バケット名までしか指定していないので、フォルダもファイルも全部出てきます。

シェルスクルリプト

#!bin/bash

##### 定数 #####
bucket=$1    #バケット名
keyword=$2   #抽出するファイル名に含まれる文字列
profile=$3   #プロファイル名
filename=$4  #作成するファイル名


##### S3から取得してマニフェストの書式で文字列を作成 #####
#1,aws cliの実行結果を``でコマンド実行して取得(配列)
array=(`aws s3 ls $bucket --recursive --profile $profile | grep $keyword`) #cliコマンド
#2,配列の要素数を取得
count=`echo ${#array[@]}`


##### 作成したいマニフェストの書式で文字列を作成 #####
#1,配列の要素を一つづつ取り出して文字列を作成
for i in `seq 0 $count`
do
    #2,配列の要素iに検索文字列が含まれていれば変数に入れる
    searchArr=`echo ${array[i]} | grep $keyword`
    #3,変数の文字数が0より大きければ文字列作成
    if [ `echo ${#searchArr}` -gt 0 ] ; then
      arr_sort+=( "{\"url\":\"s3://"$bucket"/"${array[i]}"\", \"mandatory\":true}," )
    fi
done


##### 配列を並び替え #####
#1,デフォルト設定をキープ
orig_ifs=$IFS
#2,「改行で区切る」設定に変更
IFS=$'\n'
#3,昇順で整列 (注意:@じゃなく*。sort -rで降順)
arr=($(echo "${arr_sort[*]}" | sort))
#4,デフォルト設定に戻す
IFS=$orig_ifs


##### 作成した配列arrの最後の要素から「 , 」を除外 #####
#1,配列のサイズを取得
arr_size=${#arr[@]}
#2,配列の最後の要素を指定して「 , 」を除外した文字列を作成
str=${arr[arr_size-1]%,*}
#3,変数の中身が数値の場合は$は不要?配列の要素番号を指定する際は$はいらない?
unset arr[arr_size-1]
#4,配列の最後に追加 (注意:""で囲まないと何故か文字列中のカンマで配列の別要素にされてしまう)
arr+=("$str")


##### マニフェスト作成 #####
#1,JSON形式で作成を始める
echo "{" > $filename
echo "  "entries": [" >> $filename
#2,配列arrからマニフェスト作成&表示
for (( i = 0; i < `expr ${#arr[@]}`; i++ )); do
  echo '    '${arr[i]} >> $filename
done
#3,閉じる
echo "  ]" >> $filename
echo "}" >> $filename


##### 標準出力 #####
echo "{"
echo "  "entries": ["
for (( i = 0; i < `expr ${#arr[@]}`; i++ )); do
  echo '    '${arr[i]}
done
echo "  ]"
echo "}"

1行目、これがないとスクリプトとして判断してくれません。
4〜7行目、引数を変数に入れます。$0はスクリプト名で、$1以降がコマンド引数となります。

実行

s3の構成を確認

s3://バケット名
      |
      `---test
           |
           |---test.manifest
           |
           |---tablea_1.csv
           |
           |---tableb.csv
           |
           `---test_sub
                |
                `---tablea_2.csv

ファイルが4つ入っています。
マニフェストに使用するのは「.csv」の3つです。

スクリプト実行

$ sh create_manifest.sh バケット名 test_table プロファイル名 test.manifest
{
  entries: [
    {"url":"s3://バケット名/test/test_sub/tablea_2.csv", "mandatory":true},
    {"url":"s3://バケット名/test/tablea_1.csv", "mandatory":true},
    {"url":"s3://バケット名/test/tableb.csv", "mandatory":true}
  ]
}

作成したファイルの中身

{
  entries: [
    {"url":"s3://バケット名/test/test_sub/tablea_2.csv", "mandatory":true},
    {"url":"s3://バケット名/test/tablea_1.csv", "mandatory":true},
    {"url":"s3://バケット名/test/tableb.csv", "mandatory":true}
  ]
}

結果から下記が実現されている事を確認しました。
・昇順の並び(table_aの1と2は順不同でOKとする)
・検索文字列を含むファイルを別のフォルダからも取得
・不要ファイルの除外

実際のS3の内部はもっと都合の良い構成で作ると思いますが、こういう感じになりますよという例です。

さいごに

まだまだ勉強中ですので改善点が有れば加筆修正しようと思います。