View Full Version : [PHP]Nao gosto da minha função recursiva


Armadillo
02-01-2008, 17:29
Já me mandou o serviço do apache abaixo umas nao-sei-quantas vezes e é lento que nem um caracol!
Sugestoes para melhorar o meu script serao muito bem aceites!
O que o meu codigo faz é devolver num vector unidimensional (requisito imprescindivel) todas as pastas existentes, dado um caminho inicial.


<?php


//exemplo de utilizacao da funcao
//Para devolver todos os ficheiros e directorios num vector, dado um caminho
// $filestructure = pesquisa_dir_recursivamente('caminho/a/pesquisar');

//Para devolver todos os ficheiros e directorios num vector, dado um caminho e uma extensao de ficheiro
// $filestructure = pesquisa_dir_recursivamente('caminho/a/pesquisar', 'extensao');

function pesquisa_dir_recursivamente($directorio, $filtro=FALSE)
{

if(substr($directorio,-1) == '/') //se o caminho tiver no fim uma barra
{
$directorio = substr($directorio,0,-1); // removemos.
}


if(!file_exists($directorio) || !is_dir($directorio)) //se caminho for invalido ou nao for um directorio...
{
return FALSE; // ... retorna FALSE e sai da funcao
}elseif(is_readable($directorio)) // ... se o caminho eh valido
{
$directorio_list = opendir($directorio); // abrimos o directorio
while (FALSE !== ($ficheiro = readdir($directorio_list))) // e procuramos items nele
{
if($ficheiro != '.' && $ficheiro != '..') //se apontador de ficheiro nao for o actual directorio ou o directorio pai
{
$path = $directorio.'/'.$ficheiro; //construimos o novo caminho a pesquisar
if(is_readable($path)) // se o caminho for valido
{
$subdirs = explode('/',$path); // dividimos o novo caminho em directorios
if(is_dir($path)) // se o novo caminho for um directorio
{
$directorio_tree[] = array($path,
pesquisa_dir_recursivamente($path, $filtro)); // pesquisamos o novo caminho chamando esta funcao (recursividade)

}
}
}
}

closedir($directorio_list); // fechar directorio
return $directorio_tree; // retornar a lista de ficheiros
}
else
{ // se o caminho nao for valido
return FALSE; // ... retorna FALSE
}
}


//testando funcoes

function listaArray(&$item2, $key)
{

global $pastas;
//echo "$item2<br>\n";

if (!is_array($item2) && !is_null($item2)) //se nao for um vector
{
$pastas[]=$item2; //adiciona item ao vector
}
array_walk($item2, 'listaArray');
}


$array = pesquisa_dir_recursivamente('..\..'); //ler disco em busca de dirs
array_walk($array, 'listaArray'); //retornar apenas as dirs

$pastas=array_unique($pastas); //remover possiveis duplicacoes nas pastas(caso dos null)
sort($pastas);
echo '<pre>';
var_dump ($pastas);
echo '</pre>';

Também nao consigo fazer uma pesquisa por um caminho do tipo "c:\programas", apenas "..\..\".
Acho que o codigo esta bem comentado. Se tiverem duvidas em relação a alguma coisa, buzinem
Ja agora, estou a usar a versao 4.4.3 do PHP.
:msmiley1:

inginheiiro
02-01-2008, 17:32
não sei o estas a fazer concretamente, mas não é aconselhavel faze-lo (recursivamente ou não) nesse modelo especifico.

existem muitas "variaveis" não deterministicas neste problema/plataforma que inviabilizam uma solução recursiva optima e funcional...


Alternativas:

1. Lês um nivel apenas e permites ao utilizador inter-agir , lendo 1 nivel de cada vez quando se selecciona uma pasta. podes/deves usar ajax para isso. (+- como as tree views)

2. Fazes um webservice que implemente a tua função recursiva, e chama-lo asyncronamente do php. (já não mexo em php à algum tempo mas penso já ser possivel faze-lo em php)

3. Fazes uma função/webservice que te retorna o nivel que desejas apenas, e implementas a lógica/recursividade usando ajax (javascript) :)


ainda assim optava pela solução 1 ou 3.

/ing

Armadillo
02-01-2008, 17:44
Pois é, so que isto é para ser implementado num sistema passivo, ou seja, nao vai haver interacção com o utilizador.
Em relação aos niveis, torna-se-me impossivel de saber quantos niveis é que terei que ler, visto que este script vai correr de forma autónoma, num servidor ao qual nao tenho acesso directo e o meu script tem que ser suficientemente polivalente para procurar um determinado ficheiro numa localização qualquer.
Em relação ao ajax, penso que só é possivel utiliza-lo com um webbrowser (não tenho a certeza), coisa que nao vai acontecer no meu caso (executo os php atraves do wget despolotado através do cron) e tambem porque, na minha opiniao, nao se coaduna com o espirito do meu projecto (carago, isto ficou caro em palavras! ;)).

Sei que sao muitas limitações que imponho mas tem que ser executado desta forma.
Ainda assim, agradeço a vossa ajuda.

Obrigado.

inginheiiro
02-01-2008, 17:52
se o vais chamar do cron, pq usas Php?

pq não fazes um script em perl/java etc que faça isso e retorne o respectivo XML ?

neste caso/modelo já te aconselharia o uso de recursividade .

/ing

Armadillo
02-01-2008, 18:04
1. nao fui eu que decidi, ja estava implementado parte do sistema em php e para a integração/interacção entre os varios modulos ser mais rapida optou-se por continuar a usar php.

2. nao sei se o nosso servidor corre java ou perl (riam-se de mim!, eu sou amigo do bill, que é que se há-de fazer?).
Tem uma distro de linux altamente modificada e nao convem instalarmos mais nada a nao ser com o que ja vem de origem (questao de compatibilidade com futuras actualizações do fabricante da maquina/os). Apenas sei que aquela coisa corre php 4.4.3.
Tenho acesso ao servidor muito limitado, nao posso andar a inventar pq senao tadinho do programador... Tou tipo do Liedson, parece um quartel general ;)

inginheiiro
02-01-2008, 18:14
:),

posso ainda sugerir o uso de bash.

echo "<pre>" >out.txt
find /home -name '*' >> out.txt
echo "</pre>" >>out.txt



sempre resulta , depois só tens que tratar a listagem :)

/ing

Armadillo
02-01-2008, 18:20
vais-me desculpar, mas executo isso no php? como se fosse na command line (sei que é possivel, nao me lembro é da sintax)?

edit:
ou seja:

$resultCommandLine=executacommandline( find /home -name '*');
$tmp = explode(char(13), $resultCommandLine);
//... etc bla-bla-bla
humm?

slack_guy
02-01-2008, 18:26
nao sei se o nosso servidor corre java ou perl
epa.. um servidor *NIX sem Perl é como um jardim sem flores! :-)

Vejo aqui uma excelente oportunidade para te debruçares sobre o Perl ;-) isso que pretendes fazes em 6 (vá lá... 7) linhas de código.

inginheiiro
02-01-2008, 18:29
vais-me desculpar, mas executo isso no php?? como se fosse na command line (sei que é possivel, nao me lembro é da sintax)?

Se quiseres podes chamar atraves do php, mas tava a pensar no seguinte:

fazes um ficheiro bash(usa o chmod para tornar o ficheiro executavel) em linux .
depois chama-lo a partir do cron, e este gera um ficheiro ou.txt
depois podes ler esse fich do php.


se o ficheiro se chamar script.bash , chama-lo da seguinte forma: ./script.bash /home

referencias bash http://www.linux.org/docs/ldp/howto/Bash-Prog-Intro-HOWTO.html#toc5
referencias do find http://www.linuxdevcenter.com/linux/cmd/cmd.csp?path=f/find


#!/bin/bash
echo "<pre>" >out.txt
find $1 -name '*' >> out.txt
echo "</pre>" >>out.txt


/ing

Armadillo
02-01-2008, 18:35
Como ja disse:

Tenho acesso ao servidor muito limitado, nao posso andar a inventar pq senao tadinho do programador... Tou tipo do Liedson, parece um quartel general ;)

:iconlock:riam-se riam-se, oxalá nuca vos aconteça a vós, nem consigo imprimir um naco de codigo!:005:

vamos la malta, tem que ser em php, nada de brainfuck ou perl.:lol:
nao me abandonem...

Se quiseres podes chamar atraves do php, mas tava a pensar no seguinte:

fazes um ficheiro bash(usa o chmod para tornar o ficheiro executavel) em linux .
depois chama-lo a partir do cron, e este gera um ficheiro ou.txt
depois podes ler esse fich do php.


se o ficheiro se chamar script.bash , chama-lo da seguinte forma: ./script.bash /home

referencias bash http://www.linux.org/docs/ldp/howto/Bash-Prog-Intro-HOWTO.html#toc5
referencias do find http://www.linuxdevcenter.com/linux/cmd/cmd.csp?path=f/find


#!/bin/bash
echo "<pre>" >out.txt
find $1 -name '*' >> out.txt
echo "</pre>" >>out.txt


/ing

eh pá, isso parece muita volta so pra fazer um list aos dirs existentes num outro dir.
e depois, nao me parece muito pratico ter um bash a ser chamado pelo cron, que irá gerar um ficheiro, e depois despolotar o script principal (e se o 2º começa antes do 1º acabar, sabe-se lá, os servidares as vezes ficam tolos!).

Alguem se lembra do comando para executar um comando na shell atraves do php?
Acham esta a solução mais limpinha e simples de fazer o que eu quero em PHP?

Obrigado a todos.

slack_guy
02-01-2008, 18:41
mas explica lá em duas linhas qual é o INPUT e qual é o OUTPUT do script?

Armadillo
02-01-2008, 18:47
basicamente tenho que pesquisar uns ficheiros (nao sei quais, por isso pode ir por parametro a extensao ou localizo-os posteriormente sabendo em que directórios tenho que pesquisar) que estao localizados sabe-se lá onde e importar o conteudo desses ficheiros para a bd em mysql.
Posso ter que lidar com uns valentes milhares ou centenas de milhares desses ficheiros e por uma arvore de directorios manhosa.

PS:Slack_guy, tu deves pensar que eu tenho ideias malucas (já me ajudaste em algumas), mas nao sou eu, ve na minha assinatura o link 'isto é a minha vida...'. É a pura verdade

Obrigado

K0mA
02-01-2008, 19:10
Onde é que eu já vi (http://www.techzonept.com/showthread.php?t=219290)isto...

Todas a soluções de listar ficheiros numa pasta e sub-pastas que vi até agora recorriam a recursividade...
Poderás é tentar separar as coisas para não sobrecarregar o sistema.

Função 1 - obter o caminho de todas as pastas e subpastas > $caminhos = array()
Função 2- For each $caminhos as $caminho > $ficheiros = array()
...

EDITADO: ou então visitas este sitezeco (http://pt2.php.net/scandir).

slack_guy
02-01-2008, 19:23
basicamente tenho que pesquisar uns ficheiros
isso quer dizer que tens de abrir, ler e procurar por determinado padrão dentro dos ficheiros? ou é pelos nomes dos ficheiros?

Será tipo isto?
Dada a pasta /home/slack como ponto de partida, percorre todos os ficheiros e pastas e devolve o conteúdo de todos os ficheiros com extensão 'txt' ou 'dat' que contenham a expressão 'EXPRESSAO'.

Se é isto, a única coisa que me falta saber é... em que formato queres o output? Aqui tens duas opções:
1) são devolvidos os nomes (e respectivos caminhos) dos ficheiros;
2) é criado um ficheiro com determinadas marcas (XML?) com todos os conteúdos dos ficheiros encontrados. É devolvido o nome e o caminho deste ficheiro. O teu programa encarrega-se de fazer o parse do ficheiro.

Tyran
02-01-2008, 19:53
Para correres comandos, vê aqui (http://pt.php.net/manual/en/function.popen.php)
tem lá tmb como correres os scripts

cumpzz

Armadillo
03-01-2008, 10:20
EDITADO: ou então visitas este sitezeco (http://pt2.php.net/scandir).

Nao posso usar scandir porque estou a usar a versao 4.4.3 do PHP.



Slack_guy, apenas preciso da localização dos ficheiros definidos por uma prefixo no nome do ficheiro (ex: c:\data\export\2001\12\FACT_xxxx_xxxxxx.YYY | c:\data\export\2002\1\1233\AST_xxxxxx.YYY). Para processar o conteudo dos ficheiros, nao preciso de nada, pois ja esta feito há uns aninhos.

Em relação ao output preciso de um vector unidimensional com o nome e caminho dos ficheiros.

Para simplificar, e ja tendo uma função que pesquisa os ficheiros dado um determinado prefixo e num determindado directorio, neste momento apenas preciso dos directorios existentes num determinado caminho de origem num vector unidimensional.

Espero ter sido claro e explicito,
Obrigado pessoal pela ajuda.

Armadillo
15-01-2008, 16:10
ainda continuo com o problema dos caminhos dos directório...
Alguma alma caridosa que me ajude :confused:

AragTey
15-01-2008, 16:28
Boas eu não sei nada de PHP....apenas tou a ver o algoritimo com base no que sei de C, mas esta parte aqui



$subdirs = explode('/',$path); // dividimos o novo caminho em directorios
if(is_dir($path)) // se o novo caminho for um directorio
{
$directorio_tree[] = array($path,
pesquisa_dir_recursivamente($path, $filtro));
}


nao percebo o que faz o explode, mas não é $subdirs que fica com a path do novo directorio?

Armadillo
15-01-2008, 16:45
o que o explode faz é dividir uma string num array de strings, sendo dado um caracter "divisor"
Acho que essa linha se pode ignorar. (vou comentar essa linha)

É assim, a função funciona lindamente, excepto com com caminhos do tipo /opt/site/_Site1 ou c:\programas\site\_Site1\
, ate agora só consegui por isto a funcionar com caminhos do tipo .. e ../..

Gracias

AragTey
15-01-2008, 17:13
$directorio_tree[] = array($path, pesquisa_dir_recursivamente($path, $filtro));

o que acontece quanto é returnado o valor FALSE na funcao? E se a mesma e returnar um array de chars?

Armadillo
15-01-2008, 17:25
se retornar false sai da funcao, ou seja, nao existem dirs dada a primeira directoria.
nao percebi a tua ultima questao.

cumpz

AragTey
15-01-2008, 17:30
Sim, mas se retornar uma caminho, esse caminho é adicionado ao $directorio_tree[]?

Armadillo
15-01-2008, 17:33
sim, cria um array de array, aka matriz.
se fizeres um var_dump dá-te um resultado semelhante ao que obeterias no MS-DOS ao executar o comando tree

mcog_blaster
15-01-2008, 17:55
function DirTree(RecursiveDirectoryIterator $dir)
{
$tree = array();
$dirs = array(array($dir, &$tree));

for($i = 0; $i < count($dirs); ++$i)
{
$d =& $dirs[$i][0];
$tier =& $dirs[$i][1];

for($d->rewind(); $d->valid(); $d->next())
{
if ($d->isDir())
{
$tier[$d->getFilename()] = array();
$dirs[] = array($d->getChildren(), &$tier[$d->getFilename()]);
}
else
{
$tier[$d->getFilename()] = $d->getSize();
}
}
}

return $tree;
}

$dir = new RecursiveDirectoryIterator(getcwd());
$d = DirTree($dir);
var_export($d);

Experimenta.
Funciona em php 5.1.2