Emacsやnkfを使ってFTPのアスキーモードを再現してみる(バイナリモード -> アスキーモードへのファイル変換)

「index.cgiを実行っと。あれ?500 Internal Server Errorだ...」
FTPにはBinary modeとASCII modeがあります。この2つには微妙な違いがあって、またその違いによりCGIで冒頭の「500 Internal Server Error」が発生する原因になります。
この記事ではその辺の詳細解説と、Emacsでアスキーモードを利用する方法、またシェルのコマンドレベルでアスキーモードを利用する方法を解説します。正確にはバイナリモードで転送してしまってエラーが出るファイルをアスキーモードの形に変換するという話です。それでは本論に入ります。
目次
FTPはバイナリモードで行うべき?
エンジニアの人は「FTPはバイナリモードで行っておけば問題ない」的な話しを先輩から聞いたことがあるかも知れません。そしてこれは概ね正しいのですが稀に問題が置きます。
昨今のFTPソフトは自動判定モードがありますのでそれを使った場合は問題ないかとは思いますが、あえてバイナリーモードを選んだり、FTPコマンドを使った場合などに問題が起こるケースがあります。それ以外にもcgiのzipファイルをローカルで解凍してからFTPで転送するのではなく、サーバーに転送してからzipを解凍した場合にも問題が発生する場合があります。
"500 Internal Server Error"
この問題というのはCGIの場合に起こります。htmlやcss、js、phpなどでは起こりません。起こるのは改行コードを解釈する場合です。
FTPのバイナリモードとアスキーモードの違い
ここでFTPのバイナリモードとアスキーモードの違いを見ておきます。「バイナリモードはファイルを何も変更せずに転送、アスキーモードはサーバー側の環境に文字コードや改行コードを合わせて転送」とざっくり思っている方もいるかもしれませんがこれは違います。
実際には
文字コード | 改行コード | |
---|---|---|
バイナリモード | そのまま | そのまま |
アスキーモード | そのまま | 転送先に合わせる |
こういった形です。勘違いしやすいですが「文字コードは変更されません」。変更されるのは「改行コード」のみです。
WindowからLinuxサーバーへの転送
具体例で見てみます。Windowsで作ったファイルをLinuxサーバーに転送するケースです。
この場合まずWindowsで作ったテキストファイルの文字コードは「shift_jis」で、改行コードは「CRLF」です。このファイルをFTPのアスキーモードでLinuxサーバーに転送すると文字コードは「shift_jis」のままで改行コードのみ「LF」に変更されます。
これを表で表すと
文字コード | 改行コード | |
---|---|---|
Windows | shift_jis | CRLF |
--> FTPのアスキーモードでLinuxサーバーに転送
文字コード | 改行コード | |
---|---|---|
Linuxサーバ | shift_jis | LF |
となります。
MacからLinuxサーバーへの転送
同様にMacで作成したファイルのケースも見てみます。
文字コード | 改行コード | |
---|---|---|
Mac | utf-8 | LF |
--> FTPのアスキーモードでLinuxサーバーに転送
文字コード | 改行コード | |
---|---|---|
Linuxサーバ | utf-8 | LF |
となります。
現在のmacOSは、OSX時代からUnixベースになりました。なので基本的な部分ではLinuxと似ています。なおOSX以前のMacの場合は改行コードに「CR」を使うなど現在から見ると少し変則的です。
改行コードをCGIで解釈できないと
改行コードをCGIで解釈できない場合に「構文エラーとなり "500 Internal Server Error"」が発生します。そしてLinuxサーバーでは文字コードは"utf-8"か"euc-jp"の場合がありますが、改行コードは「LF」です。
ですのでより具体的な解説としては「CRLF」の改行コードを持つCGIファイルをLinuxサーバーで実行させた時にエラーが発生します。
もうひとつのCGIの文字コードの問題
改行コードだけでなく文字コードの問題もあります。
もしnkf等を使って下記のようにファイルを変換してしまうと
文字コード | 改行コード | |
---|---|---|
元ファイル | shift_jis | CRLF |
変換したファイル | utf-8 | LF |
エラーにはならないものの「文字化け」が発生したりします。これは元ファイルが"shift_jis"だった為、下記の様にhtmlレベルで文字コードにshift_jisが指定してあるだろうからです。
<span style="color: #7C7C7C;"><!-- html5 --></span> <span style="color: #cdc9c9;"><</span><span style="color: #8b8989;">meta</span> <span style="color: #cdc9c9;">charset</span><span style="color: #cdc9c9;">=</span><span style="color: #8AE234;">"shift_jis"</span> <span style="color: #cdc9c9;">/></span> <span style="color: #7C7C7C;"><!-- html --></span> <span style="color: #cdc9c9;"><</span><span style="color: #8b8989;">meta</span> <span style="color: #cdc9c9;">http-equiv</span><span style="color: #cdc9c9;">=</span><span style="color: #8AE234;">"Content-Type"</span> <span style="color: #cdc9c9;">content</span><span style="color: #cdc9c9;">=</span><span style="color: #8AE234;">"text/html; charset=shift_jis"</span><span style="color: #cdc9c9;">/></span>
ですので改行コードだけでなく文字コードまで変更してしまうと問題が起きることがあります。この辺はCGIの作者がどこまで考慮しているかにも依りますね。
シェルで改行コードの確認
改行コードを確認する方法を解説します。いくつか方法があるかと思いますが "od" コマンドが手軽です。実行結果の末尾が "\r\n" であれば改行コードは「CRLF」、"\n" であれば「LF」です。下記に例を載せておきます。
% od -c ftp-ascii-test-sjis-CRLF.cgi <span style="color: #8c8c8c;">0000000 # ! / u s r / l o c a l / b i n 0000020 / p e r l \r \n \r \n p r i n t " 0000040 C o n t e n t - t y p e : t e 0000060 x t / h t m l \ n " ; \r \n p r i 0000100 n t " \ n " ; \r \n p r i n t 0000120 " < h t m l > \ n " ; \r \n p r i 0000140 n t " < h e a d > \ n " ; \r \n 0000160 p r i n t " < m e t a c h a 0000200 r s e t = \ " s h i f t _ j i s 0000220 \ " > \ n " ; \r \n p r i n t " 0000240 < t i t l e > 203 e 203 X 203 g < / t 0000260 i t l e > \ n " ; \r \n p r i n t 0000300 " < / h e a d > \ n " ; \r \n p 0000320 r i n t " < b o d y > \ n " ; 0000340 \r \n p r i n t " 202 261 202 352 202 315 C 0000360 G I 202 ̃ ** e 203 X 203 g 202 ł ** 267 201 B 0000400 \ n " ; \r \n p r i n t " < / b 0000420 o d y > \ n " ; \r \n p r i n t 0000440 " < / h t m l > \ n " ; \r \n \r \n 0000460</span> % od -c ftp-ascii-test-utf8-LF.cgi <span style="color: #8c8c8c;">0000000 # ! / u s r / l o c a l / b i n 0000020 / p e r l \n \n p r i n t " C o 0000040 n t e n t - t y p e : t e x t 0000060 / h t m l \ n " ; \n p r i n t 0000100 " \ n " ; \n p r i n t " < h t 0000120 m l > \ n " ; \n p r i n t " < 0000140 h e a d > \ n " ; \n p r i n t 0000160 " < m e t a c h a r s e t = \ 0000200 " U T F - 8 \ " > \ n " ; \n p r 0000220 i n t " < t i t l e > テ ** ** ス 0000240 ** ** ト ** ** < / t i t l e > \ n " 0000260 ; \n p r i n t " < / h e a d > 0000300 \ n " ; \n p r i n t " < b o d 0000320 y > \ n " ; \n p r i n t " こ ** 0000340 ** れ ** ** は ** ** C G I の ** ** テ ** ** 0000360 ス ** ** ト ** ** で ** ** す ** ** 。 ** ** \ 0000400 n " ; \n p r i n t " < / b o d 0000420 y > \ n " ; \n p r i n t " < / 0000440 h t m l > \ n " ; \n \n 0000453</span>
シェルからnkfで変換
次にFTPのバイナリモードで転送してしまったファイルを変換する方法を解説します。ASCIIモードで再度転送しなくても変換することが可能です。変換にはnkfを利用します。
文字コードは「shift_jis」で改行コードが「CRLF」のファイルを、文字コードを変えずに改行コードのみ「LF」にしてみます。
% nkf -Lu --overwrite file or % nkf -sLu --overwrite file
こんな感じで変換できます。パラメーターを解説すると、-s が「shift_jisで出力」、-Lu が「改行コードをUnix形式(LF)で出力」、--overwrite が「出力結果でファイルを上書き」です。
findと組み合わせれば一括置換も可能です。例えばこんな感じです。
% find . -iregex ".+\.cgi" -exec nkf -sLu --overwrite {} \; or % find . -iregex ".+\.cgi" | xargs nkf -sLu --overwrite
Emacsで改行コードの確認
同様の事をEmacsでもやってみます。
まず改行コードの確認は
M-x describe-coding-system (<f1> C) RET or M-x describe-current-coding-system
で行えます。
例えば文字コードが「シフトJIS」で改行コードが「CRLF」というWindowsで一般的なファイルであればこの様に表示されます。
Coding system for saving this buffer: S -- japanese-shift-jis-dos (alias: shift_jis-dos sjis-dos)
「japanese-shift-jis-dos」 の "japanese-shift-jis" 部分が文字コード、"dos"の部分が改行コードです。"dos"は「CRLF」を表します。
また文字コードが「utf-8」で改行コードが「LF」というLinuxやMacで一般的なファイルであればこの様に表示されます。
Coding system for saving this buffer: U -- utf-8-unix (alias: mule-utf-8-unix utf8-unix UTF8-unix UTF-8-unix)
同様に「utf-8-unix」の "utf-8" 部分が文字コード、"unix" 部分が改行コードです。"unix"は「LF」を表します。
Emacsで改行コードのみLFに変換
Emacsで改行コードのみ変更するには下記のように操作します。
C-x RET f (M-x set-buffer-file-coding-system) unix
変更したいファイルを開き、"C-x RET f" とタイプし "unix" とだけ入力します。これで文字コードはそのままで改行コードのみ変更されます。
ちなみに文字コードも変更したい時は同様の操作で
C-x RET f (M-x set-buffer-file-coding-system) utf-8-unix
といった感じで指定します。
[参考] EmacsでFTP
trampを使ったFTPの利用
ちなみにEmacsではtrampを使えばFTPも利用できます。ファイルを転送したり、サーバーにあるファイルを直接編集したりといった事が可能です。trampはそれなりに細かい設定も可能ですが今回はFTP部分を抜粋してみます。
<span style="color: #7C7C7C;">;;</span><span style="color: #7C7C7C;">------------------------------------------------------------------------ </span><span style="color: #7C7C7C;">;; </span><span style="color: #7C7C7C;">trampの設定 </span><span style="color: #7C7C7C;">;; </span><span style="color: #7C7C7C;">------------------------------------------------------------------------ </span><span style="color: #8c8c8c;">(</span><span style="color: #96CBFE;">require</span> '<span style="color: #99CC99;">tramp</span><span style="color: #8c8c8c;">)</span> <span style="color: #7C7C7C;">;; </span><span style="color: #7C7C7C;">FTPはPassiveモードを利用する </span><span style="color: #8c8c8c;">(</span><span style="color: #96CBFE;">setq</span> ange-ftp-try-passive-mode t<span style="color: #8c8c8c;">)</span> <span style="color: #7C7C7C;">;; </span><span style="color: #7C7C7C;">FTPのbinary/asciiモード </span><span style="color: #7C7C7C;">;; </span><span style="color: #7C7C7C;">binary転送したいファイルを正規表現で指定 </span><span style="color: #7C7C7C;">;; </span><span style="color: #7C7C7C;">その他はアスキーモードで転送される </span><span style="color: #7C7C7C;">;; </span><span style="color: #7C7C7C;">default: "" (ver24.1から変更された) </span><span style="color: #8c8c8c;">(</span><span style="color: #96CBFE;">setq</span> ange-ftp-binary-file-name-regexp <span style="color: #8AE234;">"^.*\\.\\(jpe?g\\|gif\\|png\\)$"</span><span style="color: #8c8c8c;">)</span> <span style="color: #7C7C7C;">;; </span><span style="color: #7C7C7C;">エラーが出る時は指定を変える </span><span style="color: #8c8c8c;">(</span><span style="color: #96CBFE;">setq</span> dired-listing-switches <span style="color: #8AE234;">"-alFh"</span><span style="color: #8c8c8c;">)</span>
trampは裏でange-ftpを利用しているので、ange-ftpのパラメーターを変更することで「Binary/ASCII mode」の指定や「Active/Passive mode」の切り替えが可能です。
接続時にユーザー名とパスワードを毎回打っても可能ですが、それはあまり現実的ではない為認証情報を ~/.netrc で管理します。こんな感じで定義します。
machine example.com login ftpusername password ftppass
なおhostsファイルを使えばmachine名に実際のホスト名ではなく任意の名称を使うことが可能です。
上記を設定した状態で "C-x C-f" (M-x find-file) 等を実行してミニバッファに下記の様にホスト名とサーバー上のファイル名やディレクトリ名を入れればOKです。EmacsからFTPでサーバー上のファイルを直接編集できます。
/ftp:example.com:/public_html/index.cgi
また同様の指定でdiredを使ったFTPでのファイル転送も可能です。
diredを使ってFTPクライアント化
ちなみにEmacsをよりFTPクライアントらしくする事も可能です。下記のパラメーターを追加します。
<span style="color: #8c8c8c;">(</span><span style="color: #96CBFE;">setq</span> dired-dwim-target t<span style="color: #8c8c8c;">)</span>
設定後 "C-x 2" (M-x split-window-below) や "C-x 3" (M-x split-window-right) などでウインドウを分割します。そしてそれぞれのウインドウで"M-x find-dired"などで「PCのディレクトリ」と「サーバのディレクトリ」を開きます。そうすると後は通常のdiredの操作でFTPクライアントの様にファイル転送が行えます。
例えばPC側のウインドウでファイルやディレクトリを選んで "C" を押して実行すれば、サーバー側にファイルやディレクトリを丸ごと転送可能です。ほとんどFTPクライアントライクな使い方が可能です。
なおtrampではftp以外にも、sshやscp、rsyncなども使えます。これはサーバー側のウインドウをどのメソッドで開いたかに依存します。例えば "C-x C-f /ssh:example.com:/..." とすればsshといった感じで。
一言
仕組みを理解すれば予想外のエラーにもスムーズに対応できます。それでは長くなりましたがこの辺で。