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

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;">&lt;!-- html5  --&gt;</span>
<span style="color: #cdc9c9;">&lt;</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;">/&gt;</span>

<span style="color: #7C7C7C;">&lt;!-- html --&gt;</span>
<span style="color: #cdc9c9;">&lt;</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;">/&gt;</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    "   &lt;   h   t   m   l   &gt;   \   n   "   ;  \r  \n   p   r   i
0000140    n   t       "   &lt;   h   e   a   d   &gt;   \   n   "   ;  \r  \n
0000160    p   r   i   n   t       "   &lt;   m   e   t   a       c   h   a
0000200    r   s   e   t   =   \   "   s   h   i   f   t   _   j   i   s
0000220    \   "   &gt;   \   n   "   ;  \r  \n   p   r   i   n   t       "
0000240    &lt;   t   i   t   l   e   &gt; 203   e 203   X 203   g   &lt;   /   t
0000260    i   t   l   e   &gt;   \   n   "   ;  \r  \n   p   r   i   n   t
0000300        "   &lt;   /   h   e   a   d   &gt;   \   n   "   ;  \r  \n   p
0000320    r   i   n   t       "   &lt;   b   o   d   y   &gt;   \   n   "   ;
0000340   \r  \n   p   r   i   n   t       " 202 261 202 352 202 315   C
0000360    G   I 202    &#771;  **   e 203   X 203   g 202   &#322;  ** 267 201   B
0000400    \   n   "   ;  \r  \n   p   r   i   n   t       "   &lt;   /   b
0000420    o   d   y   &gt;   \   n   "   ;  \r  \n   p   r   i   n   t
0000440    "   &lt;   /   h   t   m   l   &gt;   \   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       "   &lt;   h   t
0000120    m   l   &gt;   \   n   "   ;  \n   p   r   i   n   t       "   &lt;
0000140    h   e   a   d   &gt;   \   n   "   ;  \n   p   r   i   n   t
0000160    "   &lt;   m   e   t   a       c   h   a   r   s   e   t   =   \
0000200    "   U   T   F   -   8   \   "   &gt;   \   n   "   ;  \n   p   r
0000220    i   n   t       "   &lt;   t   i   t   l   e   &gt;  &#12486;  **  **  &#12473;
0000240   **  **  &#12488;  **  **   &lt;   /   t   i   t   l   e   &gt;   \   n   "
0000260    ;  \n   p   r   i   n   t       "   &lt;   /   h   e   a   d   &gt;
0000300    \   n   "   ;  \n   p   r   i   n   t       "   &lt;   b   o   d
0000320    y   &gt;   \   n   "   ;  \n   p   r   i   n   t       "  &#12371;  **
0000340   **  &#12428;  **  **  &#12399;  **  **   C   G   I  &#12398;  **  **  &#12486;  **  **
0000360   &#12473;  **  **  &#12488;  **  **  &#12391;  **  **  &#12377;  **  **  &#12290;  **  **   \
0000400    n   "   ;  \n   p   r   i   n   t       "   &lt;   /   b   o   d
0000420    y   &gt;   \   n   "   ;  \n   p   r   i   n   t       "   &lt;   /
0000440    h   t   m   l   &gt;   \   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 (&lt;f1&gt; 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&#12398;&#35373;&#23450;
</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&#12399;Passive&#12514;&#12540;&#12489;&#12434;&#21033;&#29992;&#12377;&#12427;
</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&#12398;binary/ascii&#12514;&#12540;&#12489;
</span><span style="color: #7C7C7C;">;; </span><span style="color: #7C7C7C;">binary&#36578;&#36865;&#12375;&#12383;&#12356;&#12501;&#12449;&#12452;&#12523;&#12434;&#27491;&#35215;&#34920;&#29694;&#12391;&#25351;&#23450;
</span><span style="color: #7C7C7C;">;; </span><span style="color: #7C7C7C;">&#12381;&#12398;&#20182;&#12399;&#12450;&#12473;&#12461;&#12540;&#12514;&#12540;&#12489;&#12391;&#36578;&#36865;&#12373;&#12428;&#12427;
</span><span style="color: #7C7C7C;">;; </span><span style="color: #7C7C7C;">default: "" (ver24.1&#12363;&#12425;&#22793;&#26356;&#12373;&#12428;&#12383;)
</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;">&#12456;&#12521;&#12540;&#12364;&#20986;&#12427;&#26178;&#12399;&#25351;&#23450;&#12434;&#22793;&#12360;&#12427;
</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クライアントの様にファイル転送が行えます。

emacs-dired-ftp.gif

例えばPC側のウインドウでファイルやディレクトリを選んで "C" を押して実行すれば、サーバー側にファイルやディレクトリを丸ごと転送可能です。ほとんどFTPクライアントライクな使い方が可能です。

なおtrampではftp以外にも、sshやscp、rsyncなども使えます。これはサーバー側のウインドウをどのメソッドで開いたかに依存します。例えば "C-x C-f /ssh:example.com:/..." とすればsshといった感じで。

一言

仕組みを理解すれば予想外のエラーにもスムーズに対応できます。それでは長くなりましたがこの辺で。