phpのexec()で実行パスを通す方法【Apache・Ubuntu18.04】

こんにちは、中原電車区のトトロです。

今日(2/4)の早朝、このサイトを運営しているサーバーのアップデートが大量に来ていたので適当に apt upgrade でアップデートしたら、
YouTubeMP3もどきの内部処理で使っているyoutube-dlというpythonソフトがphpのexec()関数から sh: 1: youtube-dl: not found みたいなエラーが出て実行できなくなってしまったので(原因は後述)、
解決策のPHPのexec()でコマンドラインを実行しようとした時の実行パスを通す方法をメモ書き程度にまとめておきます。
ついでにPHPのロケール(英語のままだとコマンド打つ時に日本語が消える)を日本語に変更できるみたいなので、その手順も備忘録がてら解説してあります。

注意

今回はトラブル元のyoutube-dlを取り上げて解説していますが、恐らく他のソフトでも方法としては使えると思います。
また、このサーバーはUbuntu 18.04なので(当然)Ubuntu向けの記事になっています。
CentOSの場合は一部Apacheの実行ユーザー名や設定ファイルの場所が変わってくるようなので、適宜読み替えて下さい(丸投げ)
私は基本exec()しか使いませんが他のshell_exec()とかpassthru()とかsystem()とかの類似のコマンドライン実行関数でも(おそらく)使えると思います。

かなり内容が冗長なのはご愛嬌…

(ちなみに、どうしてこうなったのかはアプデでApacheかphp関連の設定が書き換わったか何かだとは思いますが正確にはよく分かりません)

原因

元々phpのexec()とかshell_exec()とかいっぱいあるコマンド実行系の関数は、
基本的にコマンドをコマンドの実行ファイルがあるフルパス
(例:phpなら which php と打って出て来たphpの実行ファイルがあるフルパスを指定して実行する(例:/usr/bin/php --version))のが通例みたいなのですが、
個人的には全部フルパス指定はちょっと面倒だなあというのと、今までは普通にフルパスを指定しなくても実行できていたので普通にコマンドを実行させていました。

ここから本題ですが、
まず、通常ログインしているrootなどのユーザーの環境変数($PATH・パス)はWebブラウザ経由で実行しているphpには適用されません。
そのため、いくら自分がログインしているユーザーのパスを通しても全く効きません…

Apache上でphpを動作させている場合、exec()から実行するコマンドはApacheの実行ユーザー(Ubuntuであれば www-data 、CentOSなら apache )権限で実行されます。

つまり、今までyoutube-dlが普通に実行出来ていたのは、youtube-dlの実行ファイルがある /usr/local/bin がwww-dataユーザーの環境変数$PATHの中に入っていたからのようです。(環境変数をご存知ない方は調べてどうぞ)

試しにphpで exec(“echo $PATH”) と書いて実行し環境変数の状態を調べてみると、
案の定 /usr/sbin:/usr/bin:/snap/bin (うろ覚え)くらいしか入っていなかったので、

どうやらyoutube-dlが実行できなくなったのは何かの拍子でApacheの実行ユーザーであるwww-data の環境変数 $PATH の値に youtube-dl の実行ファイルがある  /usr/local/bin が入らなくなったことが原因と思われます。
それでシェルが「(youtube-dlなんてソフトこのパス通ってる中に)ないです」とエラー吐いていたようです…(-_-;)(ここまでたどり着くのに大変だったorz)

ちなみに、

  • youtube-dlはpython製ソフト
  • pipを使ってインストールしていた
  • aptでもインストールできるがバージョンが相当古い
  • pipはどうやらデフォルトで実行ファイルを /usr/local/bin に作るらしい
  • aptは普通に実行ファイルを /usr/bin に作る

みたいなので、youtube-dlのapt版を入れると( /usr/bin にはパスが通っているので)普通にphpから実行できるという奇妙な事が発生していました。
最初はpipを疑ったんですが見当違いだったようです…(同じpython製のStreamlinkもphp上から実行できなかったのですが同じくパスが通ってなかったのが原因のようです)

また、youtube-dlのフルパスを指定すれば当然実行できます。
(例:/usr/local/bin/youtube-dl --version)

解決策

解決策はphpを動かしているApacheの実行ユーザー( www-data or apache )の環境変数$PATHに /usr/local/bin を追加すればパスが通るので実行できるという話なのですが、その$PATHにパスを追加するのが通常の方法とは異なります(ここハマりました…(-_-;))

まずwww-dataユーザーにはホームディレクトリなんてもんはないし、
そもそもたとえ /home/www-data/.bashrc を作ったとしても読み込んでくれないので.bashrcとかに追記する方法は使えず(後述)、
PHPのexec()から export $PATH=$PATH:/usr/local/bin と実行して$PATHを設定しても、その設定はexec()の処理が終了する際に消えてしまうようです。
(同じphpファイル内の別のexec()でも環境変数の情報は引き継がれないっぽい(-_-;))

ということでApacheの実行ユーザーの環境変数をどこで設定するのかを調べていたのですがなかなか方法が見つからず完全にハマり、
適当に海外のQ&Aサイトを漁ってるとAskUbuntuにこんな記事を発見。

(Google翻訳済み)

「(前略)Apacheはwww-dataの下でその設定ファイルの内容のみを使用してbash.bashrcなどのタイプのファイルの内容を使用しないでコマンドを生成します。
私はLinuxに慣れていないので、私は確かに言うことはできません。」

とのこと。(.bashrcとかが効かなかったのはそういう事らしい)
記事を見てみると、どうやらApacheの実行ユーザーの環境変数は
/etc/apache2/envvars で指定されているらしいので、お好きなエディタで編集します。
(CentOSの場合は場所が別っぽいので探してください(丸投げ))

sudo nano /etc/apache2/envvars

48行くらいあるので、ファイルの末尾に

export PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin"

と追記します。(こうするとApacheの起動時に自動でwww-dataユーザーにこの環境変数の値が設定されるみたいです)

ちなみに、Apacheで実行する際のPHPのデフォルトロケール(地域)(=www-dataのロケール)はデフォルトでC、つまりデフォルトのロケール(=英語)になっている影響で、
日本語の文字列をexec()に投げると空白になったり文字化けしたりしてしまいます。
(どうやらここの記事曰くPHPはロケールに合わない文字は一切通さないらしく、
escapeshellarg()に引数を通す場合も日本語が全て消えてしまうらしい)

このファイルの26行目でロケールを設定する環境変数$LANGが設定されているので、
ついでにロケールを日本語にしてしまいましょう。

export LANG=C

の所を

export LANG=ja_JP.UTF-8

に書き換えます(コメントアウトして追記でも可)。

私の場合はこんな感じになりました。

編集し終わったらCtrl+Xで書き込んで終了し、
service apache restart でApacheを再起動します。

Apacheの再起動後、exec()の /usr/local/bin/ へのパスが通って正常にyoutube-dlが実行できるようになりました。めでたしめでたし。

その他・余談

テストしてみましたが、ちゃんと実行パスもロケールも変わってます。
ちゃんと日本語も消されずに認識してるようです。

クソプログラムですがソース置いとくので良かったらテストにでも使ってください。
test.php

<?php
// display_errorsをONに設定
ini_set('display_errors', 1);

// 実行するコマンド
$cmd = "(実行テストしたいコマンド) 2>&1";

// コマンド
exec('echo $PATH '."2>&1", $phpPATH); // 環境変数$PATHを調べる
exec('echo $LANG '."2>&1", $phpLANG); // 環境変数$LANGを調べる

exec($cmd, $opt, $return); // コマンドを実行

// 出力
echo '<b>PHPの$PATH(実行パス)を表示:</b>'.$phpPATH[0]."<br>\n";
echo '<b>PHPの$LANG(ロケール)を表示:</b>'.$phpLANG[0]."<br><br>\n";

echo "<b>コマンド:</b>".$cmd."<br>\n";
echo "<b>コマンドの実行結果:</b>".$return."<br><br>\n";

echo "<b>コマンドの実行ログ:</b><br>\n";

for($i = 0; $i < count($opt); $i++){
	echo $opt[$i]."<br>\n";
}
?>

今までずっとPHPの環境変数はどこに設定してあるのか疑問でしたがまさかApacheの設定の中にあるとは…
…となるとCLI版のPHPならrootでログインしてるならrootの環境変数がPHPにも適用される、という事みたいです(初耳学)

YouTubeMP3もどきはyoutube-dlのラッパー的なサイトでURL解析・DL・変換などの処理を殆どyoutube-dlとffmpegに投げているのでコマンドラインが実行できないのは致命的だったので復旧できて良かったです…
一度設定したので今後パスが通ってなくてってことはまずないと思いたいですが…

それにしても皆さん絶対パスで書いちゃうせいかPHPの実行パス設定について書いているページが殆どなくて(日本語になると尚更)かなり大変でした…

コメント