J’ai été récemment ammené à parser un nombre important de fichiers de log compressés avec Gzip.
Je voulais traiter chaque fichier à la volée, ne pas avoir à écrire chaque fichier décompressé. Pour ce dernier point, rien de plus simple, il suffit d’utiliser les options “-cd” de gzip qui permettent de décompresser et de rediriger vers la sortie standard.
Voici ce à quoi je voulais arriver :
user@hostname$ gzip -cd mon_fichier_de_log.gz | ./parser.php
Concernant le parser PHP, j’ai utilisé les streams qui permettent notament de récupérer ce qui est écrit sur la sortie standard (comme ça tombre bien, n’est ce pas
).
La version simple
1. Le traitement du flux
<?php
$input = fopen('php://stdin', 'r'); // ouverture du flux
while (!feof($input)) {
$line = fgets($input); // lecture ligne par ligne
parse($line); // ma fonction pour parser
}
fclose($input); // fermeture du flux
2. Rendre le script php exécutable
Définir le bang dans le script PHP :
#!/usr/bin/php
<?php
Fixer l’attribut exécutable :
user@hostname$ chmod +x parser.php
3. L’exécution
user@hostname$ gzip -cd mon_fichier_de_log.gz | ./parser.php
Une version plus complète
CI-dessous une version plus structurée, facilement extensible dans laquelle il est possible d’appliquer plusieurs traitement.
<?php
interface ICommandBuilder {
public function addCommand(ICommand $command);
}
interface ICommand {
public function run($str);
public function finalize();
}
/**
* Read input on stdin and call a list of ICommand on each readed line.
*/
class Phpipe implements ICommandBuilder {
/**
* Worker list, all workers are called for each readed line.
* @var array of ICommand object
*/
protected $worker = array();
public function __contruct() {}
/**
* Append a new worker
* @param ICommand $command
* @return self
*/
public function addCommand(ICommand $command) {
array_push($this->worker, $command);
return $this;
}
/**
* Read stdin until feof
*/
public function read() {
$input = fopen('php://stdin', 'r');
$exclude = array();
while (!feof($input)) {
$line = fgets($input);
foreach($this->worker as $worker) {
$worker->run($line);
}
}
fclose($input);
$this->finalize();
}
/**
* Call all ICommand::finalize
*/
protected function finalize() {
foreach($this->worker as $worker) {
$worker->finalize();
}
}
}
/**
* ICommand Null pattern
*/
class LineNull implements ICommand {
public function run($str) {
return;
}
public function finalize() {
return;
}
}
/**
* ICommand Set line number and echo line.
*/
class LineEcho implements ICommand {
public function run($str) {
!isset($this->i) ? $this->i = 0 : null;
echo ++$this->i, ': ', $str;
}
public function finalize() {
echo "\n";
}
}
/**
* ICommand Echo each line reversed.
*/
class LineReverse implements ICommand {
public function run($str) {
echo strrev(trim($str))."\n";
}
public function finalize() {}
}
/**
* ICommand Append each line in a buffer. Buffer is echoed on finalieze() call.
*/
class LineBuffer implements ICommand {
public $buffer = '';
public function run($str) {
$this->buffer .= $str;
}
public function finalize() {
echo $this->buffer;
}
}
$worker = new LineEcho;
$phpipe = new Phpipe;
$phpipe->addCommand(new LineNull)
->addCommand($worker)
->addCommand(new LineReverse)
->addCommand(new LineBuffer);
$phpipe->read();
echo "terminated\n";
Pour aller plus loin
- Permettre la sélection du flux en entrée
- Utiliser les stream de filtrage

no comment untill now