FTP便利ツール

2021/APR/02

更新履歴
日付 変更内容
2020/AUG 新規作成
2020/APR/02 Version 26

目次


はじめに

ちょいちょいこのサイト kon page を更新しております。

昔ながらのftpコマンドの対話モードでファイルをアップロードしたり削除したり。

ダウンロード方向は wget コマンドで良いのですが、 アップロード方向の手間が面倒です。

瞬間芸でちょっとだけ自動化してみました。


条件

音楽ファイルの分割 2020冬 に続き、 このツールも pythonのユーティリティ・プログラム 2020冬 の使用が前提です。

kon_pageのpythonモジュールのインストール の手順でどこかに置いて、kon_page.pth でパスを通しておきましょう。;-p)


Version 1

ftp_ut.py

既にこれで完成してる感もありますが...

使い方

機能は基本的にファイルのアップロードと削除。

ftpサイトにログインして、引数で指定したパスのファイルをアップロードしたり、 削除したりします。

あとディレクトリの作成と削除を入れてみましたが、 ちょっと使い勝手が悪いかもです。

ファイルのパスはカレントディレクトリからの相対ディレクトリ指定します。

ftpサイトにログインしたときのサイトのディレクトリが、 カレントディレクトリと対応します。

$ ./ftp_ut.py
Usage: ./ftp_ut.py -h
  host user passwd path ..
  host user passwd del path ..
  host user passwd mkdir path ..
  host user passwd rmdir path ..

サイトにログインしたときに foo/ というサブディレクトリがあるとします。

例えばカレントディレクトリ相対で

index.html
foo/bar.html

のindex.html, bar.htmlの2つのファイルをアップロードする場合は

$ ./ftp_ut.py ftp.hoge.fuga.jp kondoh xxxx index.html foo/bar.html
open ftp.hoge.fuga.jp
user kondoh ----
bin
prompt
put index.html
cd foo
lcd foo
put bar.html
cd ..
lcd ..
quit

# しばらく待機

$

実行するコマンドを表示してから、実行して終了します。

上記の2ファイルを削除する場合は

$ ./ftp_ut.py ftp.hoge.fuga.jp kondoh xxxx del index.html foo/bar.html
open ftp.hoge.fuga.jp
user kondoh ----
bin
prompt
del index.html
cd foo
del bar.html
cd ..
quit

# しばらく待機

$

ディレクトリの作成

サブディレクトリへのファイルのアップロードは、「ディレクトリが有る事」が前提です。

なので、ディレクトリが無い場合は、作ってからアップロードします。

サイト側のディレクトリの有無まで自動で判定をさせようとすると、 ちょっと難しくなるので、手動に頼ります。

例えばサイト側に

foo
  bar
    hoge
    fuga
  kon

つまり

foo/bar/hoge
foo/bar/fuga
foo/kon

を作りたい場合

$ ./ftp_ut.py ftp.hoge.fuga.jp kondoh xxxx mkdir foo/bar/hoge foo/bar/fuga foo/kon
open ftp.hoge.fuga.jp
user kondoh ----
bin
prompt
mkdir foo
mkdir foo/bar
mkdir foo/bar/hoge
mkdir foo
mkdir foo/bar
mkdir foo/bar/fuga
mkdir foo
mkdir foo/kon
quit

# しばらく待機

$

2回目の "mkdir foo" では既に存在するのでエラーになりますが ...

まぁ後続の処理は続行されるでしょう。

そこを考慮して避けようとすると、複雑になるのでサボってます。

ディレクトリの削除

引数の指定方法は作成と同じですが、動作が違うのでご注意を。

例えばサイト側のディレクトリ

foo
  bar
    hoge
    fuga
  kon

つまり

foo/bar/hoge
foo/bar/fuga
foo/bar
foo/kon
foo

を削除したい場合。

深い空のディレクトリから順に削除していく必要があります。

ディレクトリにファイルが残ってると削除に失敗するので、 ファイルは既に無くなっている事が前提です。

1つのパスの指定につき、一番深いディレクトリが1つ削除されるだけです。

まず

$ ./ftp_ut.py ftp.hoge.fuga.jp kondoh xxxx rmdir foo/bar/hoge
open ftp.hoge.fuga.jp
user kondoh ----
bin
prompt
rmdir foo/bar/hoge
quit

この実行では foo/bar にあるhoge が1つ削除されるだけです。

なのでfoo以下を全て手動で指定する必要があります。

$ ./ftp_ut.py ftp.hoge.fuga.jp kondoh xxxx rmdir \
foo/bar/hoge \
foo/bar/fuga \
foo/bar \
foo/kon \
foo \

open ftp.hoge.fuga.jp
user kondoh ----
bin
prompt
rmdir foo/bar/hoge
rmdir foo/bar/fuga
rmdir foo/bar
rmdir foo/kon
rmdir foo
quit

# しばらく待機

$

プログラムで手抜きした分、指定方法を良く考える必要があります。;-p)


Version 2

サイトのディレクトリのファイル一覧を表示する機能を追加してみました。

パッチ

v2.patch

$ cat v2.patch | patch -p1

実行例

$ ./ftp_ut.py
Usage: ./ftp_ut.py -h -v
  host user passwd path ..
  host user passwd del path ..
  host user passwd mkdir path ..
  host user passwd rmdir path ..
  host user passwd ls path ..
  host user passwd lst path ..

ls と lst を追加しました。

ls は FTP の表示のままです。

例えば、まずローカル側にファイルを用意。

$ mkdir -p hoge/fuga
$ echo foo > hoge/fuga/foo.txt
$ echo bar > hoge/fuga/bar.txt
$ mkdir -p hoge/kon
$ echo hello > hoge/kon/hello.txt

$ find hoge
hoge
hoge/fuga
hoge/fuga/bar.txt
hoge/fuga/foo.txt
hoge/kon
hoge/kon/hello.txt

サイトにディレクトリを作って...

$ ./ftp_ut.py ftp.hoge.fuga.jp kondoh xxxx mkdir hoge/fuga hoge/kon
open ftp.hoge.fuga.jp
user kondoh ----
bin
prompt
mkdir hoge
mkdir hoge/fuga
mkdir hoge
mkdir hoge/kon
quit

サイトにファイルをアップロードします。

$ ./ftp_ut.py ftp.hoge.fuga.jp kondoh xxxx hoge/fuga/foo.txt hoge/fuga/bar.txt hoge/kon/hello.txt
open ftp.hoge.fuga.jp
user kondoh ----
bin
prompt
cd hoge/fuga
lcd hoge/fuga
put foo.txt
cd ../..
lcd ../..
cd hoge/fuga
lcd hoge/fuga
put bar.txt
cd ../..
lcd ../..
cd hoge/kon
lcd hoge/kon
put hello.txt
cd ../..
lcd ../..
quit

ls を実行してみます。

$ ./ftp_ut.py ftp.hoge.fuga.jp kondoh xxxx ls hoge
drwx---r-x   4 kondoh.html.xdomain.jp 1000         4096 Feb  9 11:10 .
drwxr-xr-x  30 kondoh.html.xdomain.jp 1000         4096 Feb  9 11:10 ..
drwx---r-x   2 kondoh.html.xdomain.jp 1000         4096 Feb  9 11:14 fuga
drwx---r-x   2 kondoh.html.xdomain.jp 1000         4096 Feb  9 11:14 kon

$ ./ftp_ut.py ftp.hoge.fuga.jp kondoh xxxx ls hoge hoge/fuga
drwx---r-x   4 kondoh.html.xdomain.jp 1000         4096 Feb  9 11:10 .
drwxr-xr-x  30 kondoh.html.xdomain.jp 1000         4096 Feb  9 11:10 ..
drwx---r-x   2 kondoh.html.xdomain.jp 1000         4096 Feb  9 11:14 fuga
drwx---r-x   2 kondoh.html.xdomain.jp 1000         4096 Feb  9 11:14 kon
drwx---r-x   2 kondoh.html.xdomain.jp 1000         4096 Feb  9 11:14 .
drwx---r-x   4 kondoh.html.xdomain.jp 1000         4096 Feb  9 11:10 ..
-rw----r--   1 kondoh.html.xdomain.jp 1000            4 Feb  9 11:14 bar.txt
-rw----r--   1 kondoh.html.xdomain.jp 1000            4 Feb  9 11:14 foo.txt

lsやlstのときは、実行するコマンド群の表示はしません。

複数のパス指定すると、繋がって表示されて、ちょっとややこしいです。

lstコマンドは名前だけの表示です。

複数のパス指定すると、繋がって表示されるので、1つだけのパスを指定するのが無難です。;-p)

$ ./ftp_ut.py ftp.hoge.fuga.jp kondoh xxxx lst hoge
fuga
kon

$ ./ftp_ut.py ftp.hoge.fuga.jp kondoh xxxx lst hoge/fuga
bar.txt
foo.txt

$ ./ftp_ut.py ftp.hoge.fuga.jp kondoh xxxx lst hoge/kon
hello.txt

$ ./ftp_ut.py ftp.hoge.fuga.jp kondoh xxxx lst hoge/fuga hoge/kon
bar.txt
foo.txt
hello.txt


Version 3

もうちょっと対話的に動作させたく。

整理して修正してみると、全く別物になるレベルで書き換わってしまいました。

なるべくこれまでの使い勝手は変わらない感じにしてます。

パッチ

v3.patch

$ cat v3.patch | patch -p1

実行例

$ ./ftp_ut.py
Usage: ./ftp_ut.py [-h] [-dbg]
  host user passwd path ..
  host user passwd del path ..
  host user passwd mkdir path ..
  host user passwd rmdir path ..
  host user passwd ls path ..
  host user passwd lst path ..

$ touch foo.txt
$ mkdir foo
$ echo bar > foo/bar.txt

$ ./ftp_ut.py ftp.hoge.fuga.jp kondoh xxxx mkdir foo
login
mkdir foo

$ ./ftp_ut.py ftp.hoge.fuga.jp kondoh xxxx foo.txt foo/bar.txt
login
put foo.txt
cd foo
lcd foo
put bar.txt
cd ..
lcd ..

$ ./ftp_ut.py ftp.hoge.fuga.jp kondoh xxxx ls foo.txt foo
229 Entering Extended Passive Mode (|||60040|)
150 Opening ASCII mode data connection for file list
-rw----r--   1 kondoh.html.xdomain.jp 1000            0 Feb 10 23:38 foo.txt
226 Transfer complete
229 Entering Extended Passive Mode (|||60042|)
150 Opening ASCII mode data connection for file list
drwx---r-x   2 kondoh.html.xdomain.jp 1000         4096 Feb 10 23:38 .
drwxr-xr-x  31 kondoh.html.xdomain.jp 1000         4096 Feb 10 23:38 ..
-rw----r--   1 kondoh.html.xdomain.jp 1000            4 Feb 10 23:38 bar.txt
226 Transfer complete

$ ./ftp_ut.py ftp.hoge.fuga.jp kondoh xxxx lst foo.txt foo
foo.txt
bar.txt

$ ./ftp_ut.py ftp.hoge.fuga.jp kondoh xxxx del foo.txt foo/bar.txt
login
del foo.txt
cd foo
del bar.txt
cd ..

$ ./ftp_ut.py ftp.hoge.fuga.jp kondoh xxxx rmdir foo
login
rmdir foo

ごっそり書き換えたので、バグが残ってるかも知れません。

-debug でデバッグ様の詳細表示モードです。

同じ事をしてみると

$ ./ftp_ut.py -debug ftp.hoge.fuga.jp kondoh xxxx mkdir foo
open ftp.hoge.fuga.jp
user kondoh ---- # !!! -debug モードでは、ここが丸見えなのでご注意 !!!
bin
prompt
verbose
Interactive mode off.
Verbose mode on.
login
mkdir foo
257 "/foo" - Directory successfully created
mkdir foo

$ ./ftp_ut.py -debug ftp.hoge.fuga.jp kondoh xxxx foo.txt foo/bar.txt
open ftp.hoge.fuga.jp
user kondoh ---- # !!! -debug モードでは、ここが丸見えなのでご注意 !!!
bin
prompt
verbose
Interactive mode off.
Verbose mode on.
login
put foo.txt
local: foo.txt remote: foo.txt
229 Entering Extended Passive Mode (|||60004|)
150 Opening BINARY mode data connection for foo.txt
226 Transfer complete
cd foo
250 CWD command successful
lcd foo
Local directory now: /Users/kondoh/kon_page/ftp_ut/foo
put bar.txt
local: bar.txt remote: bar.txt
229 Entering Extended Passive Mode (|||60087|)
150 Opening BINARY mode data connection for bar.txt
226 Transfer complete
4 bytes sent in 00:00 (0.25 KiB/s)
cd ..
250 CWD command successful
lcd ..
Local directory now: /Users/kondoh/kon_page/ftp_ut
put foo.txt
cd foo
lcd foo
put bar.txt
cd ..
lcd ..

$ ./ftp_ut.py -debug ftp.hoge.fuga.jp kondoh xxxx ls foo.txt foo
  :
  略
  :

$ ./ftp_ut.py -debug ftp.hoge.fuga.jp kondoh xxxx lst foo.txt foo
open ftp.hoge.fuga.jp
user kondoh ---- # !!! -debug モードでは、ここが丸見えなのでご注意 !!!
bin
prompt
verbose
Interactive mode off.
Verbose mode on.
ls foo.txt
229 Entering Extended Passive Mode (|||60019|)
150 Opening ASCII mode data connection for file list
-rw----r--   1 kondoh.html.xdomain.jp 1000            0 Feb 11 00:15 foo.txt
226 Transfer complete
ls foo
229 Entering Extended Passive Mode (|||60026|)
150 Opening ASCII mode data connection for file list
drwx---r-x   2 kondoh.html.xdomain.jp 1000         4096 Feb 11 00:15 .
drwxr-xr-x  31 kondoh.html.xdomain.jp 1000         4096 Feb 11 00:15 ..
-rw----r--   1 kondoh.html.xdomain.jp 1000            4 Feb 11 00:15 bar.txt
226 Transfer complete
foo.txt
bar.txt

$ ./ftp_ut.py -debug ftp.hoge.fuga.jp kondoh xxxx del foo.txt foo/bar.txt
open ftp.hoge.fuga.jp
user kondoh ---- # !!! -debug モードでは、ここが丸見えなのでご注意 !!!
bin
prompt
verbose
Interactive mode off.
Verbose mode on.
login
del foo.txt
250 DELE command successful
cd foo
250 CWD command successful
del bar.txt
250 DELE command successful
cd ..
250 CWD command successful
del foo.txt
cd foo
del bar.txt
cd ..

$ ./ftp_ut.py -debug ftp.hoge.fuga.jp kondoh xxxx rmdir foo
open ftp.hoge.fuga.jp
user kondoh ---- # !!! -debug モードでは、ここが丸見えなのでご注意 !!!
bin
prompt
verbose
Interactive mode off.
Verbose mode on.
login
rmdir foo
250 RMD command successful
rmdir foo


Version 4

引数でサイトのユーザーアカウントやパスワードを指定するのが、 あまりにも煩雑なので、設定ファイルでも指定できるようにしてみました。

パスワードも設定ファイルに記述するのでご注意を。

パッチ

v4.patch

$ cat v4.patch | patch -p1

設定ファイル

YAMLファイルでファイル名はftp_ut.yaml固定です。

YAMLの辞書形式で記述します。

サイト名_a: host_a user_a passwd_a
サイト名_b: host_b user_b passwd_b
サイト名_c: host_c user_c passwd_c
  :
サイト名_z: host_z user_z passwd_z

default: サイト名_c

例えば

$ cat ftp_ut.yaml
hoge: ftp.hoge.fuga.jp kondoh xxxx
foo: foo.html.xdomain.jp foo yyyy
default: hoge

YAMLの辞書形式なので、行の順番は任意です。

キーdefaultの箇所で、デフォルトのサイト名を指定しますが、この行は省略可能です。

このftp_ut.yamlのファイル名のファイルを、 まずftp_ut.pyのあるディレクトリを探して、ファイルあればロードします。

次に、カレントディレクトリにもこのファイル名のファイルがあればロードして、 重複してる項目は上書きします。

上書きは、辞書のupdate()メソッドを使ってます。

どこにもftp_ut.yamlファイルが見つからないときは、 従来通りコマンドラインでアカウントを指定する方法のままです。

ftp_ut.yamlファイルが見つかった場合は、 コマンドラインオプションで -s サイト名 で指定してサイトを選びます。

ftp_ut.yamlファイルが見つかり -s サイト名 の指定が無いときは、 設定ファイルのデフォルトの指定でサイトが選ばれます。

候補が1つしか無い場合は、デフォルトの指定が無くても、それがデフォルト扱いになります。

サイトの情報が見つから無ければ、それなりのメッセージを表示して終了です。

ftp_ut.pyのあるディレクトリは、__file__ で取得してます。

例えば、そこにftp_ut.yamlで各サイトの一覧を用意しておいて、 ftp_ut.pyを実行するときの各作業場所のftp_ut.yamlで、 デフォルトのサイト名だけを指定する事もできます。

実行例

ftp_ut.yamlが無い状態から

$ cd /tmp
$ mkdir hoge
$ cd hoge
$ echo foo > foo.txt

$ python -m ftp_ut
Usage: /Users/kondoh/kon_page/ftp_ut/ftp_ut.py [-h] [-dbg] [-s site_name]
  host user passwd path ..
  host user passwd del path ..
  host user passwd mkdir path ..
  host user passwd rmdir path ..
  host user passwd ls path ..
  host user passwd lst path ..

  site_name in ftp_ut.yaml, can ommit host user passwd

$ python -m ftp_ut ftp.hoge.fuga.jp kondoh xxxx foo.txt
login
put foo.txt

例えば ftp_ut.py のあるディレクトリは

$ python -m site | grep ftp_ut
    '/Users/kondoh/kon_page/ftp_ut',

$ ls /Users/kondoh/kon_page/ftp_ut/ftp_ut.py
/Users/kondoh/kon_page/ftp_ut/ftp_ut.py

なので

$ echo "hoge: ftp.hoge.fuga.jp kondoh xxxx" > /Users/kondoh/kon_page/ftp_ut/ftp_ut.yaml

$ python -m ftp_ut ls foo.txt
229 Entering Extended Passive Mode (|||60053|)
150 Opening ASCII mode data connection for file list
-rw----r--   1 kondoh.html.xdomain.jp 1000            4 Feb 11 11:58 foo.txt
226 Transfer complete

他のサイトも登録

$ echo "foo: foo.html.xdomain.jp foo yyyy" >> /Users/kondoh/kon_page/ftp_ut/ftp_ut.yaml

$ cat /Users/kondoh/kon_page/ftp_ut/ftp_ut.yaml
hoge: ftp.hoge.fuga.jp kondoh xxxx
foo: foo.html.xdomain.jp foo yyyy

$ python -m ftp_ut ls foo.txt
err in .yaml, select by -s ption
  hoge
  foo
Usage: /Users/kondoh/kon_page/ftp_ut/ftp_ut.py [-h] [-dbg] [-s site_name]
  host user passwd path ..
  host user passwd del path ..
  host user passwd mkdir path ..
  host user passwd rmdir path ..
  host user passwd ls path ..
  host user passwd lst path ..

  site_name in ftp_ut.yaml, can ommit host user passwd

$ python -m ftp_ut -s hoge ls foo.txt
229 Entering Extended Passive Mode (|||60048|)
150 Opening ASCII mode data connection for file list
-rw----r--   1 kondoh.html.xdomain.jp 1000            4 Feb 11 11:58 foo.txt
226 Transfer complete

そして、この/tmp/hogeからのときは、サイトhogeをデフォルトにするためには

$ pwd
/tmp/hoge

$ echo "default: hoge" > ftp_ut.yaml

$ cat ftp_ut.yaml
default: hoge

$ python -m ftp_ut ls foo.txt
229 Entering Extended Passive Mode (|||60089|)
150 Opening ASCII mode data connection for file list
-rw----r--   1 kondoh.html.xdomain.jp 1000            4 Feb 11 11:58 foo.txt
226 Transfer complete


Version 5

やはりバグがありました。

2つの設定ファイルを読み込むときの順番が逆でした。 修正しておきます。

あと、ファイルをアップロードするときなど、 サイトにログインした直後の場所からの相対の位置で作業するのが、 少々キツイです。

chrootじゃないですが、 サイトにログインしてすぐサイト側だけcdで指定場所に移動可能にしてみました。

パッチ

v5.patch

$ cat v5.patch | patch -p1

コマンドラインからの指定方法

$ python -m ftp_ut
Usage: /Users/kondoh/kon_page/ftp_ut/ftp_ut.py [-h] [-dbg] [-s site_name] [-C dir]
  host user passwd path ..
  host user passwd del path ..
  host user passwd mkdir path ..
  host user passwd rmdir path ..
  host user passwd ls path ..
  host user passwd lst path ..

  site_name in ftp_ut.yaml, can ommit host user passwd

-C dir を追加しました。

tarコマンドにならって -C です。

指定があるとサイトにログインした直後に cd dir を実行して移動します。

ローカル側は移動せずそのままです。

例えば

$ python -m ftp_ut -s site_foo -C foo/bar index.html

では、

設定ファイルからの指定方法

従来の記述はそのまま使えます。

site_name: host user passwd

移動するディレクトリ指定を追加する場合は、

site_name: host user passwd foo/hoge

などと追加します。

元のcdしない場合と別のサイト名で用意してもかまいません。

site_name: host user passwd
site_name_hoge: host user passwd foo/hoge

':' 右側の値の文字列にスペースが含まれてない場合は特別な指定と見なします。

例えば

site_name: host user passwd
site_name_hoge: site_name/foo/hoge

他のエントリーのサイト名とディレクトリ指定を'/'で区切ってあるものとします。

例えば、作業ツリーの中で

foo/
foo/bar/hoge/

foo/ 直下と foo/bar/hoge/ 直下をよく使う場合

ftp_ut.pyのディレクトリにある方の設定ファイルftp_ut.yamlでは

site_name: host user passwd
site_foo: site_name/foo
#site_hoge: site_name/foo/bar/hoge
site_hoge: site_foo/bar/hoge

などと登録しておいて

foo/ 直下の ftp_ut.yaml では

default: site_foo

foo/bar/hoge/ 直下の ftp_ut.yaml では

default: site_hoge

にしておくと、今いてるローカルの作業ディレクトリの場所によって、 ログイン後にサイト側も移動します。


version 6

気に入らない箇所を色々と書き換えてしまいました。

コマンドと同じ名前のファイルをアップロードできないので、 一応省略可能で put を明示的に指定できるようにしました。

lst の実行結果が、ls -F 的にファイルとディレクトリを区別した表示にしました。

パッチ

v6.patch

$ cat v6.patch | patch -p1

実行例

$ python -m ftp_ut
Usage: /Users/kondoh/kon_page/ftp_ut/ftp_ut.py [-h] [-debug] [-s site_name] [-C dir]
  host user passwd [put] path ..
  host user passwd del path ..
  host user passwd mkdir path ..
  host user passwd rmdir path ..
  host user passwd ls path ..
  host user passwd lst path ..

  site_name in ftp_ut.yaml, can ommit host user passwd

ほぼ従来通り操作できると思います。

例えば、ftp_ut.yaml でデフォルトのサイトが設定されている環境で

$ mkdir -p tmp/hoge/fuga tmp/hoge/foo
$ touch tmp/bar.txt tmp/hoge/hello.txt

$ find tmp
tmp
tmp/bar.txt
tmp/hoge
tmp/hoge/foo
tmp/hoge/fuga
tmp/hoge/hello.txt

python -m ftp_ut mkdir tmp tmp/hoge tmp/hoge/foo
login
mkdir tmp
mkdir tmp
mkdir tmp/hoge
mkdir tmp
mkdir tmp/hoge
mkdir tmp/hoge/foo

$ python -m ftp_ut tmp/bar.txt tmp/hoge/hello.txt
login
cd tmp
lcd tmp
put bar.txt
cd ..
lcd ..
cd tmp/hoge
lcd tmp/hoge
put hello.txt
cd ../..
lcd ../..

$ python -m ftp_ut ls tmp
drwx---r-x   3 kondoh.html.xdomain.jp 1000         4096 Feb 14 00:04 .
drwxr-xr-x  32 kondoh.html.xdomain.jp 1000         4096 Feb 14 00:02 ..
-rw----r--   1 kondoh.html.xdomain.jp 1000            0 Feb 14 00:04 bar.txt
drwx---r-x   3 kondoh.html.xdomain.jp 1000         4096 Feb 14 00:04 hoge

$ python -m ftp_ut lst tmp
bar.txt
hoge/

$ python -m ftp_ut ls tmp/hoge
drwx---r-x   3 kondoh.html.xdomain.jp 1000         4096 Feb 14 00:04 .
drwx---r-x   3 kondoh.html.xdomain.jp 1000         4096 Feb 14 00:04 ..
drwx---r-x   2 kondoh.html.xdomain.jp 1000         4096 Feb 14 00:02 foo
-rw----r--   1 kondoh.html.xdomain.jp 1000            0 Feb 14 00:04 hello.txt

$ python -m ftp_ut lst tmp/hoge
foo/
hello.txt

$ python -m ftp_ut del tmp/hoge/hello.txt tmp/bar.txt
login
cd tmp/hoge
del hello.txt
cd ../..
cd tmp
del bar.txt
cd ..

$ python -m ftp_ut rmdir tmp/hoge/foo tmp/hoge tmp/fuga tmp
login
rmdir tmp/hoge/foo
rmdir tmp/hoge
rmdir tmp/fuga
rmdir tmp

$ rm -rf tmp


Version 7

違う環境で試してみると、色々と問題があったので修正してみました。

環境によっては、子プロセスで実行している ftp -n コマンドの標準入力に文字列を与えても、 標準出力は「だんまり」でした。

フラッシュかけてもだめ。

標準入力をクローズするまでは、返事を返してきません。

対話的にしたいので、こいうときは擬似端末。

pythonのptyモジュールを調べてみると、spawnで親側の標準入力、標準出力を変更するようなので、 別に「それ専用のコマンド」をkon_utに設けてみました。

#pty_spawn.py

パッチ

v7.patch

$ cat v7.patch | patch -p1

kon_utにpyt_spawn.pyがある前提です。

kon_pageのpythonモジュールのインストール の手順で最新版にしておきます。

操作手順などは特に変わらないはずです。


Version 8

ディレクトリを作るとき、ファイルやディレクトリの存在確認をしてから、 必要な分だけ作るようにしました。

ローカルにある深いパスのファイルをアップロードするときも、 ディレクトリが無ければputする前にmkdirするようにしました。

あと、サボってたftpサーバとの対話の同期を少しまともっぽくしました。

パッチ

v8.patch

$ cat v8.patch | patch -p1

実行例

$ mkdir -p fuga/hoge/foo
$ echo bar > fuga/hoge/foo/bar.txt

$ find fuga
fuga
fuga/hoge
fuga/hoge/foo
fuga/hoge/foo/bar.txt

$ python -m ftp_ut fuga/hoge/foo/bar.txt
login
mkdir fuga
mkdir fuga/hoge
mkdir fuga/hoge/foo
cd fuga/hoge/foo
lcd fuga/hoge/foo
put bar.txt
cd ../../..
lcd ../../..


Version 9

ディレクトリの再帰的な削除を追加しました。

rm_rf で指定のパスを再帰的に削除します。 (rm -rfのつもりです)

危険なので慎重にご使用を。

パッチ

v9.patch

$ cat v9.patch | patch -p1

実行例

$ mkdir -p fuga/hoge/foo
$ echo bar > fuga/hoge/foo/bar.txt

$ find fuga
fuga
fuga/hoge
fuga/hoge/foo
fuga/hoge/foo/bar.txt

$ python -m ftp_ut fuga/hoge/foo/bar.txt
login
mkdir fuga
mkdir fuga/hoge
mkdir fuga/hoge/foo
cd fuga/hoge/foo
lcd fuga/hoge/foo
put bar.txt
cd ../../..
lcd ../../..

$ python -m ftp_ut lst fuga fuga/hoge fuga/hoge/foo
./
../
hoge/
./
../
foo/
./
../
bar.txt

$ python -m ftp_ut
Usage: /Users/kondoh/kon_page/ftp_ut/ftp_ut.py [-h] [-debug] [-s site_name] [-C dir]
  host user passwd [put] path ..
  host user passwd del path ..
  host user passwd mkdir path ..
  host user passwd rmdir path ..
  host user passwd ls path ..
  host user passwd lst path ..
  host user passwd rm_rf path ..

$ python -m ftp_ut rm_rf fuga
login
cd fuga/hoge/foo
del bar.txt
cd ../../..
rmdir fuga/hoge/foo
rmdir fuga/hoge
rmdir fuga

$ python -m ftp_ut lst fuga

$ rm -rf fuga


Version 10

ディレクトリにロック用のディレクトリを作ったり削除したりする仕組みを追加してみました。

「ゆるゆる」のロック機構です。

秒単位のタイムスタンプに頼ってます。

ロックをかけたいディレクトリの直下に _lock/ というディレクトリを作って、 その中にローカルマシンの hostname username を元にまとめた文字列を作成。

例えば 'mac-a_kondoh' だとすると、 それに現在時刻の文字列を'.'でつないだ名前のディレクトリを作成します。

成功すれば、'get lock<改行> ...' と表示します。

target_dir/
  _lock/
    mac-a_kondoh.20200216142910/

ロックをかけようとしたときに、 誰かが作った有効なロックのディレクトリが残っていたら、 ロックに失敗し、'no lock<改行> ...' と表示します。

ただし、誰かがロックをかけたまま、異常終了してロック用のディレクトリが長らく残っていて、 いつまでもロックがかけれない、なーんて事がありがちですね。

なので、ロックをかけたり外したりするときに、 ゾンビが残ってないか調べて削除するようにしてます。

で、無事ロック用のディレクトリを作成した後、 誰かが同時にかけて競合してないかを、もう一度確認します。

パッチ

v10.patch

$ cat v10.patch | patch -p1

実行例

$ python -m ftp_ut
Usage: /Users/kondoh/kon_page/ftp_ut/ftp_ut.py [-h] [-debug] [-s site_name] [-C dir]
  host user passwd [put] path ..
    :
  host user passwd lock path ..
  host user passwd unlock path ..

  site_name in ftp_ut.yaml, can ommit host user passwd

$ python -m ftp_ut mkdir foo
login
mkdir foo

$ python -m ftp_ut lock foo
get lock
  kon-no-MacBook-Air_local__kondoh_.20200216145548

$ python -m ftp_ut lst foo
./
../
_lock/

$ python -m ftp_ut lst foo/_lock
./
../
kon-no-MacBook-Air_local__kondoh_.20200216145548/

$ python -m ftp_ut unlock foo

$ python -m ftp_ut lst foo
./
../


Version 11

今更ですが、ダウンロード方向の get も作ってみました。

指定のpathがディレクトリであれば、再帰的にダウンロードします。

エントリを調べつつ取得するので、のろいです。

ローカル側に勝手にディレクトリを作って上書きするのでご注意を。

パッチ

v11.patch

$ cat v11.patch | patch -p1

実行例

$ mkdir -p fuga/hoge/foo
$ echo bar > fuga/hoge/foo/bar.txt

$ find fuga
fuga
fuga/hoge
fuga/hoge/foo
fuga/hoge/foo/bar.txt

$ python -m ftp_ut fuga/hoge/foo/bar.txt
login
mkdir fuga
mkdir fuga/hoge
mkdir fuga/hoge/foo
cd fuga/hoge/foo
lcd fuga/hoge/foo
put bar.txt
cd ../../..
lcd ../../..

$ python -m ftp_ut lst fuga fuga/hoge fuga/hoge/foo
./
../
hoge/
./
../
foo/
./
../
bar.txt

$ mv fuga fuga-

$ python -m ftp_ut get fuga
login
cd fuga/hoge//foo
lcd fuga/hoge//foo
get bar.txt
cd ../../../..
lcd ../../../..

$ find fuga
fuga
fuga/hoge
fuga/hoge/foo
fuga/hoge/foo/bar.txt

$ diff -ur fuga- fuga
$


Version 12

getの階層をたどる処理に無駄が多すぎてのろい問題はさておき、、、

find的なのを追加してみました。

指定path以下の階層のファイルを探してそのパス群を表示します。

Unix環境のfindコマンドのようにディレクトリのエントリは表示しません。

あと、get_kind() 関数にバグがあったので修正入れてます。

パッチ

v12.patch

$ cat v12.patch | patch -p1

実行例

$ mkdir -p fuga/hoge/foo
$ mkdir -p fuga/bar/guha
$ echo bar > fuga/hoge/foo/bar.txt
$ echo hello > fuga/bar/guha/hello.txt
$ echo kon > fuga/kon.txt

$ find fuga
fuga
fuga/bar
fuga/bar/guha
fuga/bar/guha/hello.txt
fuga/hoge
fuga/hoge/foo
fuga/hoge/foo/bar.txt
fuga/kon.txt

$ python -m ftp_ut fuga/hoge/foo/bar.txt fuga/bar/guha/hello.txt fuga/kon.txt
login
mkdir fuga
mkdir fuga/hoge
mkdir fuga/hoge/foo
cd fuga/hoge/foo
lcd fuga/hoge/foo
put bar.txt
cd ../../..
lcd ../../..
mkdir fuga/bar
mkdir fuga/bar/guha
cd fuga/bar/guha
lcd fuga/bar/guha
put hello.txt
cd ../../..
lcd ../../..
cd fuga
lcd fuga
put kon.txt
cd ..
lcd ..

ここまでは前置きです。

確認したかった肝心の箇所は次の通り。

$ python -m ftp_ut find fuga
fuga/bar/guha/hello.txt
fuga/hoge/foo/bar.txt
fuga/kon.txt

ここからは後始末。

$ python -m ftp_ut rm_rf fuga
login
cd fuga/bar/guha
del hello.txt
cd ../../..
rmdir fuga/bar/guha
rmdir fuga/bar
cd fuga/hoge/foo
del bar.txt
cd ../../..
rmdir fuga/hoge/foo
rmdir fuga/hoge
cd fuga
del kon.txt
cd ..
rmdir fuga

$ python -m ftp_ut find fuga
$

$ rm -rf fuga


Version 13

getの階層をたどる処理に無駄が多すぎてのろい問題を対策してみます。

mgetを使ってみたりもしましたが、改善具合やいかに?

大きなサイズのファイルがあったり、 1つのディレクトリの中に大量のファイルがあと、 mgetのレスポンス待ちで何度も'timeout'の表示が出ますが、、、

気長に待てばファイルはちゃんと取れてると思います。;-p)

パッチ

v13.patch

$ cat v13.patch | patch -p1


Version 14

サイト用の設定ファイルを読み込む処理の箇所を、 site_ut.py として「のれん分け」してみました。

現状のftp_ut.py からは使ってませんが、 対応するURLも記述しておいて、取得できるようにしてみました。

例えばyamlファイルに次のような感じで、URLの記述を追加して試します。

site_name: host user passwd
site_foo: site_name/foo
site_hoge: site_foo/bar/hoge

url:
  site_name: http://www.site_name.com
  site_hoge: http://www.site_name.com/hoge

パッチ

v14.patch

$ cat v14.patch | patch -p1

お試しの実行例

上記の ftp_ut.yaml ファイルだったとして

$ cat ftp_ut.yaml
site_name: host user passwd
site_foo: site_name/foo
site_hoge: site_foo/bar/hoge

url:
  site_name: http://www.site_name.com
  site_hoge: http://www.site_name.com/hoge
$ ./site_ut.py site_name
host: host
passwd: passwd
path: ''
url: http://www.site_name.com
user: user
$ ./site_ut.py site_foo
host: host
passwd: passwd
path: foo
url: http://www.site_name.com/foo
user: user
$ ./site_ut.py site_hoge
host: host
passwd: passwd
path: foo/bar/hoge
url: http://www.site_name.com/hoge
user: user


Version 15

site_ut.pyにget(), put()追加

site_ut.py でサイト用の設定ファイルを読み込む際に、 URL以外にもどんどん追加しやすいように拡張してみました。

とりあえず 'url' 以外に 'lmt_size' という(謎の)項目を追加してます。;-p)

そして、site_ut.py に put(), get() 関数を追加してみました。

put() は内部的にftp_utを使って、サイトにファイルをアップロードします。

get() はwgetコマンドで、指定サイトのURLからファイルをダウンロードします。

パッチ

v15.patch

$ cat v15.patch | patch -p1

お試しの実行例

$ cat ftp_ut.py
site_name: host user passwd
site_foo: site_name/foo
site_hoge: site_foo/bar/hoge

url:
  site_name: http://www.site_name.com
  site_hoge: http://www.site_name.com/hoge

lmt_size:
  site_name: 3m
$ python -m site_ut site_name
host: host
user: user
passwd: passwd
path: ''
url: http://www.site_name.com
lmt_size: 3m

$ python -m site_ut put hogehoge

$ mv hogehoge hogehoge.org

$ python -m site_ut get hogehoge


Version 16

不具合修正

site_ut.py の get で、ディレクトリ・パスの生成が抜けてました。

修正しておきます。

パッチ

v16.patch

$ cat v16.patch | patch -p1


Version 17

不具合修正

site_ut.py get コマンドで、ディレクトリを含まないファイルだけのパスを指定した場合、 途中の取得先のディレクトリへの移動が、

$ cd

相当のコマンド実行になってしまい、変な動作になってました。

修正しておきます。

合わせて、取得元のURLの存在確認をしてから取得するようにしました。

パッチ

v17.patch

$ cat v17.patch | patch -p1


Version 18

階層put

putで指定pathがディレクトリの場合、 以下の階層にあるファイルとシンボリック・リンクを、 全てputするようにしてみました。

put側は簡単に実現できますが、 get側はwgetコマンドを使う方針なので、 簡単にはいかないですね。

パッチ

v18.patch

$ cat v18.patch | patch -p1


Version 19

存在確認

ファイルやディレクトリが存在するか確認するだけの exists コマンドを追加しました。

get コマンド同様に path を指定します。

指定のファイルやディレクトリが存在すると、 found xxx と表示して、終了コード0で正常終了します。

存在しないと、 not found xxx と表示して、終了コード1で異常終了します。

内部では wget --spider コマンドを使ってます。

パッチ

v19.patch

$ cat v19.patch | patch -p1


Version 20

cat

catコマンドを追加してみました。

ファイル限定で内容を標準出力に表示します。

(テキストファイルを想定してます)

内部では wget -q -O- 結果を表示してます。

パッチ

v20.patch

$ cat v20.patch | patch -p1


Version 21

不具合

site_ut.py の getコマンド箇所にtypoがありました。 修正しておきます。

コマンド呼び出し箇所の整理

#arg.py

にget_name_args()関数を追加したので、 site_ut.py から使うようにして整理しておきます。

-C オプション追加

site_ut.pyのコマンドライン引数にオプション -C dir を追加しました。

tarコマンドと同じく、動作前にローカル側のディレクトリ移動します。

ただし、ftp_ut.yaml のロードは起動した時のディレクトリからロードするのはそのままです。

ロード後に、-C dir 指定のディレクトリに移動して後続の処理を実行します。

パッチ

v21.patch

$ cat v21.patch | patch -p1


Version 22

ftp_utでサイトの直接指定

長らくftp_ut.yamlファイル設定に頼る使い方しかしてなかったら、 直接host, user, passwdを指定する使い方が出来なくなっておりました。

見直してみると、なるほど、従来の指定の仕方では多少無理がある状態...

専用のオプション指定 -SITE "host user passwd" を追加します。

パッチ

v22.patch

$ cat v22.patch | patch -p1
$ ftp_ut -SITE "host user psswd" path ...

などと指定します。


Version 23

site_utバグ修正

ローカル側のディレクトリ指定 -C dir でのputでは、 site_utの内部でディレクトリ移動してから、 ftp_utを呼び出していたため、 ftp_utで参照するカレントディレクトリのftp_ut.yamlが、 site_utと異なってしまう事態に陥っておりました。

ディレクトリ移動した後は、 移動前に取り込んだサイトの情報を、 ftp_utで直接指定するようにして、対策してみます。

パッチ

v23.patch

$ cat v23.patch | patch -p1


Version 24

site_ut.pyのputコマンドの実行時に、だんまりの期間があまりに長いので、 -verb オプションを追加してみました。

-verb を指定して実行すると、 内部で実行するftp_ut.pyで -debug オプション付きで実行します。

コマンド実行に使っているcmd_ut.pyのcall()では、 subprocess.check_output()を使っているので、 コマンド実行の時に表示される文字列は、終了の値に返り値としてまとめて返されるため、 肝心な時に表示はされず「だんまり」のままです。

これだけのために pythonのユーティリティ・プログラム 2020冬 を更新して cmd_ut.py にcall_show()を追加してみました。

さらに、ftp_ut.pyで-debugオプションを指定したときは、 login時のパスワードも表示されてしまっていたので、 伏字にする対応も入れてみました。

パッチ

v24.patch

$ cat v24.patch | patch -p1


Version 25

ftp_ut.pyの修正

-debugオプション付きのときに、パスワード以外にも、 ホスト名やユーザも伏字にするようにしました。

site_ut.pyの修正

簡易なファイルツリーのバージョン管理ツール(gitの真似事) 2020春 の絡みで、色々と不具合があぶりだされました。

lmt_size設定の関連で、大きなファイルを分割した場合や、 URL上のファイルの存在確認などににバグがありました。

修正しておきます。

パッチ

v25.patch

$ cat v25.patch | patch -p1


Version 26

fsyn push site_utからsite_ut.py経由で使うときに、 FTPサイトのディレクトリに大量のファイルが溜まってくると、 lsのレスポンスが返るまでに、頻繁にタイムアウトするようになって しまいました。

とりあえずタイムアウトを15秒から4倍の1分にして、しのいでおきます。

v26.patch
diff -ur v25/ftp_ut.py v26/ftp_ut.py
--- v25/ftp_ut.py	2020-05-06 19:08:35.000000000 +0900
+++ v26/ftp_ut.py	2021-04-02 21:15:41.000000000 +0900
@@ -13,7 +13,7 @@

 to_str = base.to_str

-def new(host, user, passwd, tmout1=15, tmout2=1, debug=False):
+def new(host, user, passwd, tmout1=60, tmout2=1, debug=False):
 	ftp_cmd = 'python -m pty_spawn ftp -n'
 	proc = cmd_ut.proc_new_comm_tm2( ftp_cmd, tmout1, tmout2 )