nulib/php/src_os/sh.php

311 lines
10 KiB
PHP
Raw Normal View History

2024-04-25 17:46:18 +04:00
<?php
namespace nulib\os;
use nulib\cl;
use RuntimeException;
class sh {
static final function _quote(string $value): string {
if (preg_match('/^[a-zA-Z0-9_.@:,\/+-]+$/', $value)) return $value;
return escapeshellarg($value);
}
/**
* obtenir la valeur $value quotée pour le shell
*
* si c'est une valeur scalaire, elle sera quotée sous la forme 'value'
* si c'est un tableau séquentiel, elle sera quoté sous la forme ('value'...)
* sinon, elle sera quotée sour la forme ([key]=value...)
*/
static final function quote($value): string {
if (is_array($value)) {
$parts = [];
if (cl::is_list($value)) {
foreach ($value as $part) {
$parts[] = self::_quote(strval($part));
}
} else {
foreach ($value as $key => $part) {
$key = self::_quote($key);
$val = self::_quote($part);
$parts[] = "[$key]=$val";
}
}
return "(".implode(" ", $parts).")";
} else {
return self::_quote(strval($value));
}
}
/**
* obtenir une commande shell à partir du tableau des arguments.
* à utiliser avec exec()
*/
static final function join(array $parts): string {
$count = count($parts);
for($i = 0; $i < $count; $i++) {
$parts[$i] = self::_quote(strval($parts[$i]));
}
return implode(" ", $parts);
}
private static final function add_redir(string &$cmd, ?string $redir, ?string $input, ?string $output): void {
if ($redir !== null) {
switch ($redir) {
case "outonly":
case "noerr":
$redir = "2>/dev/null";
break;
case "erronly":
case "noout":
$redir = "2>&1 >/dev/null";
break;
case "both":
case "err2out":
$redir = "2>&1";
break;
case "none":
case "null":
$redir = ">/dev/null 2>&1";
break;
case "default":
$redir = null;
break;
}
}
if ($input !== null) {
$redir = $redir !== null? "$redir ": "";
$redir .= "<".escapeshellarg($input);
}
if ($output !== null) {
$redir = $redir !== null? "$redir ": "";
$redir .= ">".escapeshellarg($output);
}
if ($redir !== null) $cmd .= " $redir";
}
/**
* Corriger la commande $cmd:
* - si c'est tableau, joindre les arguments avec {@link join()}
* - sinon, mettre les caractères en échappement avec {@link escapeshellarg()}
*/
static final function verifix_cmd(&$cmd, ?string $redir=null, ?string $input=null, ?string $output=null): void {
if (is_array($cmd)) $cmd = self::join($cmd);
else $cmd = escapeshellcmd(strval($cmd));
self::add_redir($cmd, $redir, $input, $output);
}
/**
* Lancer la commande spécifiée avec passthru() et retourner le code de retour
* dans la variable $retcode. $cmd doit déjà être formaté comme il convient
*
* voici la différence entre system(), passthru() et exec()
* +----------------+-----------------+----------------+----------------+
* | Command | Displays Output | Can Get Output | Gets Exit Code |
* +----------------+-----------------+----------------+----------------+
* | passthru() | Yes (raw) | No | Yes |
* | system() | Yes (as text) | Last line only | Yes |
* | exec() | No | Yes (array) | Yes |
* +----------------+-----------------+----------------+----------------+
*
* @return bool true si la commande s'est lancée sans erreur, false sinon
*/
static final function _passthru(string $cmd, int &$retcode=null): bool {
passthru($cmd, $retcode);
return $retcode == 0;
}
/**
* Comme {@link _passthru()} mais lancer la commande spécifiée avec system().
* cf la doc de {@link _passthru()} pour les autres détails
*/
static final function _system(string $cmd, string &$output=null, int &$retcode=null): bool {
$last_line = system($cmd, $retcode);
if ($last_line !== false) $output = $last_line;
return $retcode == 0;
}
/**
* Comme {@link _passthru()} mais lancer la commande spécifiée avec exec().
* cf la doc de {@link _passthru()} pour les autres détails
*/
static final function _exec(string $cmd, array &$output=null, int &$retcode=null): bool {
exec($cmd, $output, $retcode);
return $retcode == 0;
}
/**
* Lancer la commande $cmd dans un processus fils via un shell et attendre la
* fin de son exécution.
*
* $cmd doit déjà être formaté comme il convient
*/
static final function _fork_exec(string $cmd, int &$retcode=null): bool {
$pid = pcntl_fork();
if ($pid == -1) {
// parent, impossible de forker
throw new RuntimeException("unable to fork");
} elseif ($pid) {
// parent, fork ok
pcntl_waitpid($pid, $status);
if (pcntl_wifexited($status)) {
$retcode = pcntl_wexitstatus($status);
} else {
$retcode = 127;
}
return $retcode == 0;
}
// child, fork ok
pcntl_exec("/bin/sh", ["-c", $cmd]);
return false;
}
/**
* Corriger la commande spécifiée avec {@link verifix_cmd()} puis la lancer
* avec passthru() et retourner le code de retour dans la variable $retcode
*
* $redir spécifie le type de redirection demandée:
* - "default" | null: $output reçoit STDOUT et STDERR n'est pas redirigé
* - "outonly" | "noerr": $output ne reçoit que STDOUT et STDERR est perdu
* - "erronly" | "noout": $output ne reçoit que STDERR et STDOUT est perdu
* - "both" | "err2out": $output reçoit STDOUT et STDERR
* - "none" | "null": STDOUT et STDERR sont perdus
* - sinon c'est une redirection spécifique, et la valeur est rajoutée telle
* quelle à la ligne de commande
*
* @return bool true si la commande s'est lancée sans erreur, false sinon
*/
static final function passthru($cmd, int &$retcode=null, ?string $redir=null): bool {
self::verifix_cmd($cmd, $redir);
return self::_passthru($cmd, $retcode);
}
/**
* Comme {@link passthru()} mais lancer la commande spécifiée avec system().
* Cf la doc de {@link passthru()} pour les autres détails
*/
static final function system($cmd, string &$output=null, int &$retcode=null, ?string $redir=null): bool {
self::verifix_cmd($cmd, $redir);
return self::_system($cmd, $output, $retcode);
}
/**
* Comme {@link passthru()} mais lancer la commande spécifiée avec exec().
* Cf la doc de {@link passthru()} pour les autres détails
*/
static final function exec($cmd, array &$output=null, int &$retcode=null, ?string $redir=null): bool {
self::verifix_cmd($cmd, $redir);
return self::_exec($cmd, $output, $retcode);
}
/**
* Corriger la commande spécifiée avec {@link verifix_cmd()}, la préfixer de
* "exec" puis la lancer avec {@link _fork_exec()}
*/
static final function fork_exec($cmd, int &$retcode=null, ?string $redir=null): bool {
self::verifix_cmd($cmd, $redir);
return self::_fork_exec("exec $cmd", $retcode);
}
#############################################################################
/** retourner le répertoire $HOME */
static final function homedir(): string {
$homedir = getenv("HOME");
if ($homedir === false) {
$homedir = posix_getpwuid(posix_getuid())["dir"];
}
return path::abspath($homedir);
}
/** s'assurer que le répertoire $dir existe */
static final function mkdirp(string $dir): bool {
if (is_dir($dir)) return true;
return mkdir($dir, 0777, true);
}
/** créer le répertoire qui va contenir le fichier $file */
static final function mkdirof(string $file): bool {
if (file_exists($file)) return true;
$dir = path::dirname($file);
if (file_exists($dir)) return true;
return mkdir($dir, 0777, true);
}
/**
* créer un répertoire avec un nom unique. ce répertoire doit être supprimé
* manuellement quand il n'est plus utilisé.
*
* @return string le chemin du répertoire
* @throws IOException si une erreur se produit (impossible de créer un
* répertoire unique après 2560 essais)
*/
static final function mktempdir(?string $prefix=null, ?string $basedir=null): string {
if ($basedir === null) $basedir = sys_get_temp_dir();
if ($prefix !== null) $prefix .= "-";
$max = 2560;
do {
$dir = "$basedir/$prefix".uniqid();
$r = @mkdir($dir);
$max--;
} while ($r === false && $max > 0);
if ($r === false) {
throw IOException::last_error("$dir: unable to create directory");
}
return $dir;
}
/**
* Supprimer un répertoire créé avec mktempdir
*
* un minimum de vérification est effectué qu'il s'agit bien d'un répertoire
* généré par mktempdir
*/
static final function rmtempdir(string $tmpdir, ?string $prefix=null, ?string $basedir=null): void {
if ($basedir === null) $basedir = sys_get_temp_dir();
if ($prefix !== null) $prefix .= "-";
// 13 '?' parce que c'est la taille d'une chaine générée par uniqid()
if (fnmatch("$basedir/$prefix?????????????", $tmpdir)) {
self::exec(["rm", "-rf", $tmpdir]);
} else {
throw new IOException("$tmpdir: n'est pas un répertoire temporaire");
}
}
/**
* supprimer tous les répertoires temporaires qui ont été créés avec le
* suffixe spécifié dans le répertoire $basedir
*/
static final function cleantempdirs(string $prefix, ?string $basedir=null): void {
if ($basedir === null) $basedir = sys_get_temp_dir();
$prefix .= "-";
// 13 '?' parce que c'est la taille d'une chaine générée par uniqid()
$tmpdirs = glob("$basedir/$prefix?????????????", GLOB_ONLYDIR);
if ($tmpdirs) {
self::exec(["rm", "-rf", ...$tmpdirs]);
}
}
static final function is_diff_file(string $f, string $g): bool {
if (!is_file($f) || !is_file($g)) return true;
self::exec(array("diff", "-q", $f, $g), $output, $retcode);
return $retcode !== 0;
}
static final function is_same_file(string $f, string $g): bool {
if (!is_file($f) || !is_file($g)) return false;
self::exec(array("diff", "-q", $f, $g), $output, $retcode);
return $retcode === 0;
}
static final function is_diff_link(string $f, string $g): bool {
if (!is_link($f) || !is_link($g)) return true;
return @readlink($f) !== @readlink($g);
}
static final function is_same_link(string $f, string $g): bool {
if (!is_link($f) || !is_link($g)) return false;
return @readlink($f) === @readlink($g);
}
}