質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

89.05%

Python、UnicodeEncodeErrorがPHPのexec経由の時のみ出る

解決済

回答 3

投稿 編集

  • 評価
  • クリップ 0
  • VIEW 272

Y.NINOMIYA

score 21

お世話になっております。
質問のタイトルがわかりにくく申し訳ありません。情報を詰め込んだらこうなりました。

さて、本題ですが、該当のエラー、ソースコードなどは以下になります。

エラー

'ascii' codec can't encode characters in position 0-8: ordinal not in range(128)

ソースコード

index.php

<?php
$command="/usr/bin/python3 test.py";
exec($command,$output);
?>

test.py

# -*- coding: utf-8 -*- 
import csv
import sys
data=["aaa", "bbb", "ccc"]
try:
    f = open("日本語のファイル名000.csv", 'a',newline = "" ,encoding='utf-8')
    csvWriter = csv.writer(f)
    csvWriter.writerow(data)
    f.close(data)
except Exception as e:
    error_log = open('error.log', 'w')
    error_log.write(str(e))
    error_log.close()
    raise

発生している問題

SSHでpython test.pyとするとエラー無く正常に動作します。しかし、PHPを通すと該当のエラーが発生してしまいます。

環境

Ubuntu 18.04.4 LTS
コードの編集はWinSCPから行っており、環境設定のデフォルト文字コードはUTRF-8に設定されています。

よろしくお願いします。

追記①

PHPのexec関数の引数を以下の2つで試してみましたがエラーは同じでした。

$command="LANG=ja_JP.UTF-8 /usr/bin/python3 test.py" 
$command="PYTHONIOENCODING=UTF-8 /usr/bin/python3 test.py"

追記②

index.phpを以下のように編集しましたが、依然同じエラーです。

$locale='ja_JP.UTF-8';
setlocale(LC_ALL,$locale);
putenv('LC_ALL='.$locale);

$command="/usr/bin/python3 test.py";
exec($command,$output);

また、Apacheのエラーログに少し詳細な情報が記録されていました。

Traceback (most recent call last):
  File "test.py", line 12, in <module>
    f = open("\u65e5\u672c\u8a9e\u306e\u30d5\u30a1\u30a4\u30eb\u540d000.csv", 'a',newline = "" ,encoding='utf-8')
UnicodeEncodeError: 'ascii' codec can't encode characters in position 0-8: ordinal not in range(128)

追記③

※いずれもエラーは解消されませんでした

Python側で環境変数を操作する方法
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8')を挿入しました。
PHPは元のソースコードのままです。

# -*- coding: utf-8 -*- 
import csv
import sys
import io
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8')
data=["aaa", "bbb", "ccc"]
try:
    f = open("日本語のファイル名000.csv", 'a',newline = "" ,encoding='utf-8')
    csvWriter = csv.writer(f)
    csvWriter.writerow(data)
    f.close(data)
except Exception as e:
    error_log = open('error.log', 'w')
    error_log.write(str(e))
    error_log.close()
    raise

PHP側で環境変数を操作する方法
putenv("PYTHONIOENCODING=utf-8");を挿入しました。
Pythonは元のソースコードのままです。上のPython側で環境変数を操作する方法のソースコードではありません。

<?php
putenv("PYTHONIOENCODING=utf-8");
$command="/usr/bin/python3 test.py";
exec($command,$output);
?>

Apacheの実行ユーザを変更
etc/apache2/apache2.confを編集し、Apacheの実行ユーザをwww-dataから'SSH実行ユーザ'へ変更しました。
カレントディレクトリに出力されるエラーログの所有者がwww-dataから'SSH実行ユーザ'へ変更されていたので定義は正常に変更できたものと思われます。


訂正
質問に直接的な影響は無いのですが、元のソースコードのCSVファイルを閉じる部分の最終行

f = open("日本語のファイル名000.csv", 'a',newline = "" ,encoding='utf-8')
csvWriter = csv.writer(f)
csvWriter.writerow(data)
csvWriter.close(data)


とあったのですが、正しくは

f = open("日本語のファイル名000.csv", 'a',newline = "" ,encoding='utf-8')
csvWriter = csv.writer(f)
csvWriter.writerow(data)
f.close(data)            //この行を変更


でした。エラー該当箇所が今回の訂正箇所より前にあるためプログラムが強制終了してしまい影響は無いのですが一応訂正です。
本文ソースコードは既に訂正されています。

  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 過去に投稿した質問と同じ内容の質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

質問への追記・修正、ベストアンサー選択の依頼

  • Kenji.Noguchi

    2020/04/26 15:04 編集

    $command="LANG=ja_JP.UTF-8 /usr/bin/python3 test.py";としてみては? これで動かなければ$command="PYTHONIOENCODING=UTF-8 /usr/bin/python3 test.py";

    キャンセル

  • Y.NINOMIYA

    2020/04/26 20:21

    提案ありがとうございます。
    2つとも試してみましたがエラーは変わらずでした、、、

    キャンセル

回答 3

checkベストアンサー

+4

次の行を

    f = open("日本語のファイル名000.csv", 'a',newline = "" ,encoding='utf-8')

次のように変更して、ファイル名のエンコーディングを明示的に指定します。これで動くと思いますし、環境依存にさせないためには、むしろ積極的に指定する方が安全です。

    f = open("日本語のファイル名000.csv".encode('utf-8'), 'a',newline = "" ,encoding='utf-8')

僕の最初のコメントの提案がうまく行かなかった理由はおそらくja_JP.UTF-8ロケールが存在しないためです。locale -aコマンドを僕の環境で実行するとja_JP.UTF-8はありません。Ubuntuを日本語の設定なしでインストールすると多分同じ結果になると思います。

$ locale -a
C
C.UTF-8
en_AG
en_AG.utf8
en_AU.utf8
en_BW.utf8
en_CA.utf8
en_DK.utf8
en_GB.utf8
en_HK.utf8
en_IE.utf8
en_IL
en_IL.utf8
en_IN
en_IN.utf8
en_NG
en_NG.utf8
en_NZ.utf8
en_PH.utf8
en_SG.utf8
en_US.utf8
en_ZA.utf8
en_ZM
en_ZM.utf8
en_ZW.utf8
POSIX

ちなみに端末の環境は次の通り。日本語の設定をするとログイン環境によってエラーメッセージが日本語になったり、英語になったり煩雑ですし、環境依存の問題かも?と悩む可能性が一つ増えるので英語のままにしています。これで困ったことはありません。

$ locale
LANG=en_US.UTF-8
LANGUAGE=
LC_CTYPE="en_US.UTF-8"
LC_NUMERIC="en_US.UTF-8"
LC_TIME="en_US.UTF-8"
LC_COLLATE="en_US.UTF-8"
LC_MONETARY="en_US.UTF-8"
LC_MESSAGES="en_US.UTF-8"
LC_PAPER="en_US.UTF-8"
LC_NAME="en_US.UTF-8"
LC_ADDRESS="en_US.UTF-8"
LC_TELEPHONE="en_US.UTF-8"
LC_MEASUREMENT="en_US.UTF-8"
LC_IDENTIFICATION="en_US.UTF-8"
LC_ALL=

言語はen_USでもエンコーディングはUTF-8なので、修正前のコードを試してみるとうまくいきます。

$ LANG=en_US.UTF-8 python3 test.py
$ ls
test.py  日本語のファイル名000.csv

僕の環境では存在しないロケールja_JP.UTF-8で修正前のコードを試すと同じエラーがおきます。

$ LANG=ja_JP.UTF-8 python3 test.py
Traceback (most recent call last):
  File "test.py", line 8, in <module>
    f = open("\u65e5\u672c\u8a9e\u306e\u30d5\u30a1\u30a4\u30eb\u540d000.csv", 'a',newline = "" ,encoding='utf-8')
UnicodeEncodeError: 'ascii' codec can't encode characters in position 0-8: ordinal not in range(128)

.encode('utf-8')を追加した修正後のコードであれば、存在しないロケールを無理やり指定してもエラーになりません。

$ LANG=ja_JP.UTF-8 python3 test.py

大分長くなりましたが、encode() decode()は必要最小限にし、Pythonの外の世界とやりとりする時にだけ使うようにします。例えばSTDOUTにprintしたりディスクの読み書きやネットワーク、popen(),system()などで外部コマンドを呼び出す時が外の世界になります。その時だけエンコードに注意しましょう。Pythonの内側では、出来る限り文字列はUnicodeオブジェクトのまま、バイト列はbytesのままにすれば、うまく行くようにPythonのライブラリが設計されているからです。今回のファイル名も外界になることは盲点で、僕も勉強になりました。

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

+1

下記はいかがでしょうか。

$locale='ja_JP.UTF-8';
setlocale(LC_ALL,$locale);
putenv('LC_ALL='.$locale);

$command="/usr/bin/python3 test.py";
exec($command,$output);

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2020/04/27 01:06

    回答ありがとうございます。

    上記コードでも同様のエラーでした...
    質問本文にも追記しますがApacheのエラーログから若干詳細のエラーが見れたので添付します。
    ====================================

    Traceback (most recent call last):
    File "test.py", line 12, in <module>
    f = open("\u65e5\u672c\u8a9e\u306e\u30d5\u30a1\u30a4\u30eb\u540d000.csv", 'a',newline = "" ,encoding='utf-8')
    UnicodeEncodeError: 'ascii' codec can't encode characters in position 0-8: ordinal not in range(128)

    キャンセル

+1

PHPを実行するユーザの環境変数はどうなってますか?

サーバの設定次第では、ファイル所有者ではなく、サーバ自身が実行します。
SSHでログインするユーザとは別で、環境設定が異なることがあるので注意。

ApacheのPHP実行ユーザー名を確認する

また、Python用に設定が必要かもしれない環境変数としては、
PYTHONIOENCODING がPythonでの標準出力のエンコーディングを決定します。


追記: PHP/Pythonでの対応方法

方法1: PHP側で環境変数 PYTHONIOENCODING に "utf-8" を設定。

putenv("PYTHONIOENCODING=utf-8");

方法2: Pythonスクリプト内で対応。

これは、PYTHONIOENCODINGを設定するのと同等の効果なので、どちらか片方のみで構いません。

標準出力が規定のエンコーディングに変換されるので、
環境によってはこれを合わせる必要があります。

(但し、追加で提示されたエラーとの関連はわからないので、
まずは日本語が正しく表示できるかどうかを確かめてください)

import io
import sys
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8')

python3.7 以降での対応方法

import sys
sys.stdout.reconfigure(encoding='utf-8')

お使いのPythonのバージョンに合わせて、どちらかスクリプトの先頭の方に記述します。
(バージョン依存を避けたい場合は上の方)

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2020/04/27 02:05

    もう一点訂正、"PYTHONIOENCODING=UTF-8 /usr/bin/python3 test.py" で環境変数は試されていたのですね。見落としてました。

    キャンセル

  • 2020/04/27 02:36

    プログラム内で環境変数に対応する方法を試してみましたが依然同じエラーでした、、、
    Pythonのバージョンが3.6.9でしたので前者を試しました。

    Apacheの実行ユーザをSSHログインユーザに変更することが可能でしたので定義を変更してみましたが効果はありませんでした。
    カレントディレクトリに出力される`error.log`の所有者が`www-data`からログインユーザに変更されていたので実行ユーザの変更には成功しているものと思われます。

    また、本文ソースコードに訂正があります。この後本文にも追記しますが、PythonのCSVファイルを閉じようとしている
    `error_log.close()`は、正しくは`f.close()`でした。
    この記述の前にエラー該当箇所があるのでプログラムが終了してしまい到達していませんが、、、、

    キャンセル

  • 2020/04/27 03:18

    UnicodeEncodeError: 'ascii' codec can't encode characters in position 0-8: ordinal not in range(128)

    position 9文字の「日本語のファイル名」と一致するので、英語のファイル名ではどうですか?

    標準出力のエンコーディングではなく、
    unicodeファイル名のところ、ファイルシステムのエンコーディングの設定かもしれません
    https://docs.python.org/ja/3/howto/unicode.html
    https://docs.python.org/ja/3/library/sys.html#sys.getfilesystemencoding

    影響のある環境変数は、 LANG もしくは、LC_CTYPE

    キャンセル

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 89.05%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

関連した質問

同じタグがついた質問を見る