FTPをコマンドラインから便利に使う

2010年6月18日 加藤 雄亮 このエントリーをはてなブックマークに追加 Clip to Evernote

皆さん、プログラムのデプロイはどうしていますか??

通常scpやrsync、その他のデプロイツールを使用すれば、簡単かつ確実に運用サーバにプログラムを配備することが出来ます。

しかし、利用するサーバによって、FTP接続しか出来ない場合があります。
LinuxのFTPコマンドは非常に使い勝手が悪く、ディレクトリ配下を全てアップロードする等という事が出来ません。
また、ソースコードをローカルにダウンロードしてFTPソフトウェアでアップロードする方法は効率が悪く、アップロードしたく無いファイル等は事前に削除するなど、非常にミスを誘発しそうな作業です。

サーバ上のコマンド一発でFTPアップロードが出来たら、確実に便利です。

今回は、サーバ間でFTPアップロードが出来るシェルスクリプトを作成してみました。

NcFTP Clientをインストール

まず、NcFTPという通常のFTPコマンドより使い易いコマンドを利用します。
※NcFTPは Download NcFTP Client からインストールしてください。

ソースコードからインストールする場合
# ソースコードをダウンロードします。
[user@centos ~]$ wget ftp://ftp.ncftp.com/ncftp/ncftp-3.2.4-src.tar.gz

# tarボールを解凍します。
[user@centos ~]$ tar zxvf ncftp-3.2.4-src.tar.gz

# Makeして実行ファイルを生成します。
[user@centos ~]$ cd ncftp-3.2.4
[user@centos ncftp-3.2.4]$ ./configure
[user@centos ncftp-3.2.4]$ make

# インストールします。
# デフォルトで /usr/local/bin に配置されます。
[user@centos ncftp-3.2.4]$ make install



FTPアップロード シェルスクリプト

このスクリプトは、expectという会話形式通信を自動的に行うことができるコマンドを利用します。
rsyncの–exclude-fromオプションのように、アップロードしたくないリストを記述したファイルを外部で管理する事も出来ます。

deploy.sh
#!/bin/bash

#
# deploy.sh
#
# 使用方法    : ./deploy.sh
# 設定ファイル: deploy.exclude.txt (非アップロードファイルがある場合のみ)
#

## Config ------------------------------------------------------
FTP_HOST='ftp.xxxxx.co.jp'        # FTPホスト名
FTP_USER='username'               # FTPユーザー名
FTP_PASS='password'               # FTPパスワード

SRC_DIR='/home/user/appname/'     # アップロード元ローカルディレクトリ
DEST_DIR='/'                      # アップロード先FTPサーバのログインディレクトリ
EXCLUDE_FILE='deploy.exclude.txt' # 非アップロード設定ファイル

## Parse Exclude File -------------------------------------------
parse_exclude_file()
{
    if [ ! -f $EXCLUDE_FILE ]; then
        return
    fi

    echo '[ Exclude Pattern ]'

    local base_ifs="${IFS}"
    IFS='
'
    local file=(`cat $EXCLUDE_FILE`)
    IFS="${base_ifs}"

    local line
    for line in "${file[@]}"
    do
        if echo $line | grep -q '^#'; then
            continue
        fi
        if [ -z "${line}" ]; then
            continue
        fi

        line=`echo $line | awk '{print $1}'`
        echo $line

        line=`echo $line | \
              sed 's:\.:\\\.:' | \
              sed 's:\*:\.*:' | \
              sed 's:^/:\^/:'`

        if [ -n "${EXCLUDE_PATTERN}" ]; then
            line=" ${line}"
        fi

        EXCLUDE_PATTERN="${EXCLUDE_PATTERN}${line}"
    done
    echo
}

## Create Deploy List ------------------------------------------
create_deploy_list()
{
    echo '[ Deploy Target File ]'

    local list=`find $SRC_DIR | \
                grep -v CVS | \
                grep -v .svn | \
                grep -v .git`

    local file target
    for file in $list
    do
        if [ -d $file ]; then
            file="${file}/"
        fi

        target=`echo $file | sed "s:${SRC_DIR}::"`
        if [ -z "${target}" -o "${target}" = '/' ]; then
            continue
        fi

        if is_exclude $target; then
            continue
        fi

        echo $target
        echo $target >>$TEMP_FILE
    done
    echo
}

## Is Exclude ---------------------------------------------------
is_exclude()
{
    local target=$1

    local pattern
    for pattern in $EXCLUDE_PATTERN
    do
        if echo "/${target}" | grep -q $pattern; then
            return 0
        fi
    done

    return 1
}

## FTP Upload --------------------------------------------------
ftp_upload()
{
    local base_ifs="${IFS}"
    IFS='
'
    local list=`cat $TEMP_FILE`
    IFS="${base_ifs}"

    cd $SRC_DIR
    expect - <<EOF
        set timeout -1

        spawn ncftp -u $FTP_USER -p $FTP_PASS $FTP_HOST

        expect "ncftp*>"
        send "cd $DEST_DIR\r"

        foreach file {$list} {
            expect "ncftp*>"
            if {[regexp {.*\/\$} \$file]} {
                send "mkdir \$file\r"
            } else {
                send "put -fz \$file \$file\r"
            }
        }

        expect "ncftp*>"
        send "exit\r"
EOF
    echo
}

## Clean -------------------------------------------------------
clean()
{
    if [ -f "${TEMP_FILE}" ]; then
        rm -f $TEMP_FILE
    fi
}

## Main --------------------------------------------------------
EXCLUDE_PATTERN=''
parse_exclude_file
if [ $? -ne 0 ]; then
    echo "[Error] Failed to parse the '${EXCLUDE_FILE}'." >&2
    exit $?
fi

TEMP_FILE=`mktemp`
create_deploy_list
if [ $? -ne 0 ]; then
    echo '[Error] Failed to create the source list.' >&2
    clean
    exit $?
fi

echo -n "Do you upload it? [y/n]: "
read ANSWER
if [ "${ANSWER}" != 'y' ]; then
    echo 'Canceled the upload.'
    exit
fi

ftp_upload
if [ $? -ne 0 ]; then
    echo '[Error] Upload failed.' >&2
    clean
    exit $?
fi

clean
echo '----'
echo "'${FTP_HOST}' deploy success!"




deploy.exclude.txt
#
# deploy exclude
#
# 除外ファイルの設定(除外したいファイルがある場合のみ)
#   deploy.exclude.txtファイルに除外パターンを記述する
#   例)
#     *.txt       *.txt と一致する全てのファイル名を除外
#     /foo/       ルートにある foo ディレクトリを除外
#     bar/*.log   bar ディレクトリ内の *.log に一致するファイルを除外
#

# ルートにあるappディレクトリを除外
/app/  # コメントも書けます

# ログファイルを除外
*.log




実行例

以下のようなディレクトリ構成があるとします。
app/
app/1.txt
src/
src/2.txt
var/
var/3.log

deploy.sh を実行すると、src/、src/2.txt、var/ のみがアップロード対象となります。
[user@centos ~]$ ./deploy.sh  ← プログラムを実行

[ Exclude Pattern ]
/app/
*.log

[ Deploy Target File ]
src/
src/2.txt
var/

Do you upload it? [y/n]: y  ← アップロードするか質問されます。「y」と応答

... FTPアップロード
...
...
--
deploy success.



ご利用のサーバ環境に合わせて設定を変更すれば、簡単にサーバ間のFTPアップロードが出来るので、参考にして頂ければと思います。