Utiliser des outils en ligne de commande avec PHP

| Thomas Myer | Yannick Komotir |

Apprenez à mieux intégrer les scripts avec des outils de ligne de commande. Examinons avec les commandes shell_exec(), exec(), passthru() et system() comment passer des informations en toute sécurité à la ligne de commande, et comment en récupérer. Commentez

- Titre : Utiliser des outils en ligne de commande avec PHP

- Auteurs : Thomas Myer, Yannick Komotir

- Parution : 19 janvier 2010

- Mise à jour : 14 décembre 2019

- Licence : Copyright ® 2009 Developpez LLC. Tous droits réservés Developpez LLC. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez LLC. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

Sommaire

- I. Introduction

- II. Vue d'ensemble de la ligne de commande

- II-A. shell_exec()

- II-B. passthru()

- II-C. exec()

- II-D. system()

- III. Quelques exemples

- IV. Sécurité

- V. Autres ressources

- VI. Remerciements

I - Introduction

Si vous avez déjà travaillé avec PHP, vous savez qu'il est un excellent outil pour la création de pages Web riches en fonctionnalités. Comme tout langage de script en général, PHP :

- est facile à apprendre ;

- a beaucoup de frameworks puissants et efficaces comme CakePHP et CodeIgniter, faisant de vous un programmeur aussi productif que n'importe quel autre ;

- peut communiquer avec MySQL, PostgreSQL, Microsoft ® SQL Server, et même Oracle ;

- s'intègre facilement avec les frameworks JavaScript comme script.aculo.us et jQuery.

Cependant, parfois, vous voulez faire plus, ou vous êtes obligé de faire plus. Par là, je veux dire si vous devez travailler directement avec le système de fichiers du serveur où est exécuté PHP, vous finissez par avoir besoin de travailler avec des fichiers sur le système, pour voir quels processus sont en cours d'exécution, ou exécuter d'autres tâches.

Au départ, vous avez du contenu en utilisant des commandes PHP comme file() pour ouvrir des fichiers. Cependant à un certain moment le seul moyen de faire quelque chose est d'être capable d'exécuter des commandes shell sur le serveur et récupérer une partie de la sortie. Par exemple, vous pourriez avoir besoin de savoir combien de fichiers existent dans un répertoire donné ou vous pouvez avoir besoin de connaître le nombre de lignes qui ont été écrites pour un groupe de fichiers log ou encore vous pouvez avoir le besoin de faire certains traitements sur ces fichiers en le copiant dans un autre répertoire ou utiliser rsync pour les transporter vers un autre emplacement.

Dans l'article Command-line PHP? Yes, you can!!, Roger McCoy montre comment utiliser PHP directement depuis la ligne de commande – pas de navigateur web nécessaire. Dans cet article, j'aborde le sujet d'un autre point de vue, en vous montrant comment intégrer dans vos scripts des commandes shell et d'y récupérer les données retournées dans vos interfaces et processus. Ce tutoriel se base sur la distribution Linux ® Berkeley Software Distribution (BSD), mais vous pouvez utiliser n'importe quel autre système sur base d'UNIX ®. Nous supposons que vous travaillez sur un système basé sur la pile Linux-Apache-MySQL-PHP (LAMP). Vos connaissances peuvent varier si vous utilisez une autre variante UNIX, car la disponibilité des commandes varie d'une installation à l'autre. Comme beaucoup d'entre vous développent aussi sur Mac OS X, qui exploite une variante de BSD, nous gardons l'échantillon de commandes aussi générique que possible afin d'assurer la portabilité.

II - Vue d'ensemble de la ligne de commande

L'interface en ligne de commande (CLI)/Server Application Programming Interface (SAPI) PHP a été publiée à titre expérimental depuis la version 4.2.0 de PHP. À la version 4.3.0 elle a été entièrement prise en charge et activée par défaut. PHP CLI/SAPI vous permet de développer en shell. En effet, il est possible de créer des outils en PHP qui s'exécutent directement depuis la ligne de commande. De cette manière, les développeurs PHP peuvent être aussi productifs que ceux sur Perl, AWK, Ruby, ou les scripts shell dans ce contexte.

Cet article présente les outils intégrés à PHP, qui vous permettent de puiser dans l'environnement du shell de base et du système de fichiers PHP est exécuté. PHP fournit un certain nombre de fonctions pour exécuter des commandes externes, nous avons parmi elles : shell_exec(), exec(), passthru() et system(). Ces commandes sont similaires, mais fournissent des interfaces différentes pour le programme externe que vous utilisez. Chacune de ces commandes génère un processus fils pour exécuter la commande ou le script que vous désignez, et chacune d'elles capture la sortie de votre commande comme c'est fait sur la sortie standard (stdout).

II-A - shell_exec()

La commande shell_exec() n'est juste qu'un alias de l'opérateur guillemets obliques (`). Si vous avez fait du shell ou des scripts Perl, vous savez que vous pouvez capturer la sortie d'autres commandes à l'intérieur des opérateurs guillemets obliques. Par exemple, le listing 1 montre comment utiliser les guillemets obliques pour obtenir un compte de mots pour chaque fichier texte (.txt) dans le répertoire courant.

Listing 1. Utilisation de guillemets obliques pour le nombre de mots

#! /bin/sh/bin/sh
number_of_words=`wc -w *.txt`
echo $number_of_words

# résultat serait quelque chose comme :
#165 readme.txt 388 results.txt 588 summary.txt # 165 readme.txt 388 results.txt 588 summary.txt
# etc ....

Dans votre script PHP, vous pouvez simplement exécuter cette commande à l'intérieur de shell_exec() , et obtenir les résultats dont vous avez besoin, en supposant que vous ayez des fichiers texte dans le même répertoire.

Listing 2. Exécution de la même commande avec shell_exec()

<?php
$results = shell_exec('wc -w *.txt');
echo $results;
?>

Comme vous pouvez le voir dans la figure 1, vous obtenez les mêmes résultats voulus à partir du script shell. C'est parce que shell_exec() vous permet d'exécuter un programme externe via le shell, puis renvoie le résultat sous forme d'une chaîne.

Figure 1. Résultats de l'exécution d'une commande shell par shell_exec()

S'il vous plaît, notez que vous obtiendrez le même résultat si vous utilisez seulement les guillemets obliques, comme montré ci-dessous.

Listing 3. En utilisant uniquement les opérateurs guillemets obliques

<?php
$results = `wc -w *.txt`;
echo $results;
?>

le listing 4 montre une méthode encore plus simple.

Listing 4. Une méthode plus simple

<?php
echo `wc -w *.txt`;
?>

Il est important de noter que pratiquement tout ce que vous pouvez faire sur la ligne de commande UNIX ou dans un script shell est autorisé ici. Par exemple, vous pouvez utiliser des pipes (« | ») pour enchaîner les commandes. Vous pouvez même créer un script shell avec toutes vos opérations dans le script et il suffit de composer le script shell, avec ou sans argument, au besoin.

Par exemple, si vous souhaitez compter seulement les mots dans les cinq premiers fichiers texte du répertoire, vous pouvez utiliser un pipe (« | ») pour raccorder l'ensemble du résultat des commandes wc et head. En outre, vous pouvez inclure la sortie du résultat dans les balises pre afin de les afficher dans un navigateur Web, comme illustré ci-dessous.

Listing 5. Une commande shell plus complexe

<?php
$results = shell_exec('wc -w *.txt | head -5');
echo "<pre>".$results . "</pre>";
?>

La figure 2 montre le résultat de l'exécution du script du listing 5.

Figure 2. Résultat de l'exécution d'une commande plus complexe fantôme par l'intermédiaire shell_exec()

Plus loin dans cet article, vous apprendrez à passer des arguments à ces scripts en PHP. Pour le moment, vous pouvez considérer cela comme un moyen d'exécuter des commandes shell, tant que vous n'oubliez pas que vous verrez uniquement la sortie standard. S'il y a des erreurs dans votre script ou commande, vous ne verrez pas une erreur standard stderr sauf si vous la redirigez vers stdout.

II-B - passthru()

La commande passthru() permet d'exécuter un programme externe et d'afficher le résultat à l'écran. Vous n'avez pas besoin d'utiliser echo ou return pour voir ces résultats, ils s'affichent simplement dans le navigateur. Vous pouvez ajouter un argument optionnel, une variable qui contient le code de retour du programme externe, par exemple, 0 pour le succès, ce qui est une bonne manière pour le débogage.

Dans le Listing 6, j'utilise la commande passthru() pour exécuter le petit script compteur des mots déjà exécuté dans la section précédente. Comme vous pouvez le voir, j'ai également ajouté une variable $returnval qui contient le code de retour.

Listing 6. Utilisation de la commande passthru() pour exécuter le script 'compteur des mots'

<?php
passthru('wc -w *.txt | head -5',$returnval);
echo "<hr/>".$returnval;
?>

Remarquez que je n'ai pas affiché la sortie avec un echo. Le résultat final s'affiche simplement à l'écran, comme illustré ci-dessous.

Figure 3. Résultats d'exploitation du passthru() de commande avec un code return

Dans le Listing 7, je vous présente une petite erreur dans le code en supprimant l'undescore avant le 5 dans la commande head du script.

<?php
//ci-dessous nous introduisons une erreur en enlevant le - de la commande head

passthru('wc -w *.txt | head 5',$returnval);
echo "<hr/>".$returnval;
?>

Notez que le script ne s'exécute pas comme prévu. Comme le montre la figure 4, vous obtenez un écran blanc, et une valeur de retour de 1. Ce code de retour indique généralement qu'une erreur quelconque s'est produite. Être capable d'essai pour ce code de retour, il est plus facile de comprendre ce qui doit être réparé.

II-C - exec()

La commande exec() est similaire à shell_exec() sauf qu'elle retourne la dernière ligne de la sortie et éventuellement, renvoie un tableau contentant la sortie complète de la commande et le code d'erreur. Le listing 8 est un exemple de ce qui se passe si vous exécutez exec() sans stocker le résultat dans un tableau de données.

Listing 8. Exécution de la fonction exec() sans stockage du résultat dans un tableau

<?php
$resultats = exec('wc -w *.txt | head -5');
echo $resultats;

#n'affichera que la dernière ligne du résultat, exemple:
#3847 fichier.txt
?>

Pour stocker le résultat dans un tableau, ajoutez le nom du tableau comme second argument de exec() Je le fais dans le listing 9 en utilisant $data comme nom du tableau.

Listing 9. Stockage du résultat de la commande exec() dans un tableau

<?php
$results = exec('wc -w *.txt | head -5',$data);
print_r($data);

#pourrait afficher les données stockées sous la forme:
#Array ( [0]=> 555 text1.txt [1] => 283 text2.txt)
?>

Après avoir stocké le résultat dans un tableau, vous pouvez faire quelque chose avec chaque ligne. Par exemple, vous pouvez diviser l'espace d'abord pour trouver et stocker les valeurs discrètes dans une table de base de données, ou vous pouvez appliquer un formatage spécifique ou des balises à chaque ligne.

II-D - system()

La commande system(), illustrée dans le listing 10, est hybride. Comme passthru() elle affiche tout ce qu'elle reçoit directement des programmes externes. Comme exec() elle retourne également la dernière ligne et rend le code de retour disponible.

Listing 10. La commande system()

<?php
system('wc -w *.txt | head -5');

#pourrait afficher:
#123 file1.txt 332 file2.txt 444 file3.txt
#etc...
?>

III - Quelques exemples

Maintenant que vous avez appris comment utiliser toutes ces commandes PHP, vous aurez probablement des questions. Par exemple, quelle commande utiliser et quand ? Cela dépend entièrement de vous et surtout de vos besoins.

La plupart du temps, j'utilise la commande exec() à cause des données fournies dans un tableau sans aucun traitement. Sinon, j'utilise shell_exec() pour des commandes simples, surtout si je ne me soucie pas de la sortie. Si j'ai juste besoin d'exécuter un script shell, j'utilise passthru(). Souvent, j'utilise ces fonctions pour différentes raisons et parfois de façon interchangeable. Tout dépend de mon humeur et ce que j'essaie de faire.

Vous vous posez peut-être la question : « À quoi sert tout cela ? ». Au cas où vous êtes en panne d'idées ou un projet qui saute, de même si aucune bonne manière d'utiliser les commandes shell ne s'est présentée, je vous propose ici quelques idées.

Si vous écrivez une application qui offre la possibilité de sauvegarder ou de transférer des fichiers, il est plus intelligent d'utiliser shell_exec() ou une des autres commandes citées dans cet article pour lancer un script shell rsync. Vous pouvez écrire le script shell qui contient les commandes rsync nécessaires, puis utiliser passthru() pour l'exécuter sur base d'une commande utilisateur ou d'une tâche planifiée.

Par exemple, un utilisateur avec les privilèges appropriés dans votre application, comme admin, peut avoir besoin de transférer 50 fichiers PDF à partir d'un serveur vers un autre. L'utilisateur devrait naviguer vers l'emplacement approprié dans votre application, cliquer sur Transfert, sélectionner les fichiers à transférer, puis cliquer sur Envoyer. Dans son action, le formulaire aurait un script PHP qui exécute votre script rsync via passthru() avec une variable optionnelle de retour afin de savoir si un problème est survenu, comme indiqué ci-dessous.

Listing 11. Exemple de script PHP qui exécute un script rsync via passthru()

<?php
passthru('xfer_rsync.sh',$returnvalue);

if ($returnvalue != 0){
//Nous avons un problème
//ajouter un code sur l'erreur ici
}else{
//tout est OK
//aller sur une autre page
}
?>

Si vous avez une application qui a besoin de lister les processus ou les fichiers, ou d'avoir certaines données sur ces processus ou ces fichiers, vous pouvez utiliser l'une des commandes décrites dans cet article pour faire cela. Par exemple, une simple commande grep peut vous aider à trouver des fichiers correspondant à certains critères de recherche. L'utilisation de cette commande avec exec() et le stockage du résultat dans un tableau pourraient permettre de construire un tableau HTML ou un formulaire pour exécuter d'autres commandes.

Jusqu'à présent, j'ai abordé les évènements générés par les utilisateurs : si l'utilisateur appuie sur un bouton ou clique sur un lien, un script PHP fonctionne. Vous pouvez également obtenir des effets intéressants en exécutant des scripts autonomes PHP avec crontab ou un autre ordonnanceur. Par exemple, si vous avez un script de sauvegarde, vous pouvez l'exécuter indépendamment via un cron ou vous pouvez l'insérer dans un script PHP et puis l'exécuter. Pourquoi vouloir faire cela ? Ça paraît redondant et inutile, non ? Eh bien non pas si vous considérez que vous pouvez exécuter le script de sauvegarde grâce à exec() ou passthru() puis faire un traitement en vous basant sur le code de retour. Si vous obtenez une erreur, vous pouvez écrire une entrée dans un journal d'erreur ou une base de données, ou encore envoyer une alerte e-mail. Si le script réussit, vous pouvez vider la sortie brute du script à la base de données (par exemple, rsync a un mode verbose utile dans le diagnostic des problèmes postérieurs).

IV - Sécurité

Quelques mots sur la sécurité : si vous acceptez que les utilisateurs saisissent et transmettent des informations au shell, le mieux serait de désinfecter ces données. Enlevez toute commande que vous pensez pouvoir être nuisible pouvant désactiver certaines choses, par exemple sudo (exécuté avec les privilèges super-utilisateur) ou rm (supprimer). En fait, le mieux serait d'interdire aux utilisateurs de saisir des commandes librement et de ne les autoriser qu'à choisir dans une liste des cas possibles.

Exemple, si vous exécutez un programme de transfert qui accepte une liste de fichiers comme argument, vous pourriez avoir une liste de cases à cocher pour tous vos fichiers. Les utilisateurs peuvent sélectionner, désélectionner les éléments de la liste et cliquez sur Envoyer (submit) pour exécuter le script shell rsync. Ils ne seront pas autorisés à saisir dans une liste les noms de fichiers ou d'utiliser une expression régulière.

Pour assurer un maximum de sécurité, faites passer toutes les commandes venant des utilisateurs aux fonctions escapeshellarg et escapeshellcmd qui permettent d'échapper les caractères spéciaux, afin d'éviter des attaques du type injection.

V - Autres ressources

- Exemple « CLI » sous windows

- La fonction escapeshellarg

- La fonction escapeshellcmd

VI - Remerciements

Je tiens à remercier jacques_jean, Furr et Pierre Fauconnier pour leurs relectures qui ont permis d'améliorer la qualité de cet article ainsi que Ridekick pour ses conseils avisés.