Converter saída do comando tree para o formato json

10

Existe uma maneira conveniente de converter a saída do comando * nix "tree" para o formato JSON?

Edit: Eu acho que não descrevi o meu problema o suficiente. Meu objetivo é converter algo como:

.
|-- dir1
|   |-- dirA
|   |   |-- dirAA
|   |   `-- dirBB
|   `-- dirB
`-- dir2
    |-- dirA
    `-- dirB

para dentro:

{"dir1" : [{"dirA":["dirAA", "dirAB"]}, "dirB"], "dir2": ["dirA", "dirB"]}
roundrobin
fonte
Como você esperaria ver isso encapsulado em JSON? Você poderia dar um exemplo e os resultados esperados?
Drav Sloan
@DravSloan I editada a pós para mostrar um exemplo
roundrobin
O que você esperaria obter se dir1/dirAtivesse subdiretórios?
Cjm
{"dir1" : [{"dirA":["dirAA", "dirAB"]}, "dirB"], "dir2": ["dirA", "dirB"]}
roundrobin
@BausTheBig - Eu não acho que você tenha pensado nisso o tempo todo. O treecomando não é a ferramenta certa. Eu posso estar inclinado a fazer ls -Rou em findvez disso.
slm

Respostas:

6

Tentativa 1

Uma solução usando apenas perl, retornando uma estrutura simples de hash de hashes. Antes do OP esclarecer o formato de dados do JSON.

#! /usr/bin/perl

use File::Find;
use JSON;

use strict;
use warnings;

my $dirs={};
my $encoder = JSON->new->ascii->pretty;

find({wanted => \&process_dir, no_chdir => 1 }, ".");
print $encoder->encode($dirs);

sub process_dir {
    return if !-d $File::Find::name;
    my $ref=\%$dirs;
    for(split(/\//, $File::Find::name)) {
        $ref->{$_} = {} if(!exists $ref->{$_});
        $ref = $ref->{$_};
    }
}

File::FindO módulo funciona de maneira semelhante ao findcomando unix . O JSONmódulo pega variáveis ​​perl e as converte em JSON.

find({wanted => \&process_dir, no_chdir => 1 }, ".");

Irá iterar a estrutura do arquivo do diretório de trabalho atual, chamando a sub-rotina process_dirpara cada arquivo / diretório em ".", E o comando no_chdirperl para não emitir um chdir()para cada diretório encontrado.

process_dir retorna se o presente arquivo examinado não for um diretório:

return if !-d $File::Find::name;

Em seguida, pegue uma referência do hash existente %$dirsem $ref, dividir a volta caminho do arquivo /e laço com fora adição de uma nova chave de hash para cada caminho.

Criando uma estrutura de diretórios como slm fez:

mkdir -p dir{1..5}/dir{A,B}/subdir{1..3}

A saída é:

{
   "." : {
      "dir3" : {
         "dirA" : {
            "subdir2" : {},
            "subdir3" : {},
            "subdir1" : {}
         },
         "dirB" : {
            "subdir2" : {},
            "subdir3" : {},
            "subdir1" : {}
         }
      },
      "dir2" : {
         "dirA" : {
            "subdir2" : {},
            "subdir3" : {},
            "subdir1" : {}
         },
         "dirB" : {
            "subdir2" : {},
            "subdir3" : {},
            "subdir1" : {}
         }
      },
      "dir5" : {
         "dirA" : {
            "subdir2" : {},
            "subdir3" : {},
            "subdir1" : {}
         },
         "dirB" : {
            "subdir2" : {},
            "subdir3" : {},
            "subdir1" : {}
         }
      },
      "dir1" : {
         "dirA" : {
            "subdir2" : {},
            "subdir3" : {},
            "subdir1" : {}
         },
         "dirB" : {
            "subdir2" : {},
            "subdir3" : {},
            "subdir1" : {}
         }
      },
      "dir4" : {
         "dirA" : {
            "subdir2" : {},
            "subdir3" : {},
            "subdir1" : {}
         },
         "dirB" : {
            "subdir2" : {},
            "subdir3" : {},
            "subdir1" : {}
         }
      }
   }
}

Tentativa 2

Ok, agora com estrutura de dados diferente ...

#! /usr/bin/perl

use warnings;
use strict;
use JSON;

my $encoder = JSON->new->ascii->pretty;   # ascii character set, pretty format
my $dirs;                                 # used to build the data structure

my $path=$ARGV[0] || '.';                 # use the command line arg or working dir

# Open the directory, read in the file list, grep out directories and skip '.' and '..'
# and assign to @dirs
opendir(my $dh, $path) or die "can't opendir $path: $!";
my @dirs = grep { ! /^[.]{1,2}/ && -d "$path/$_" } readdir($dh);
closedir($dh);

# recurse the top level sub directories with the parse_dir subroutine, returning
# a hash reference.
%$dirs = map { $_ => parse_dir("$path/$_") } @dirs;

# print out the JSON encoding of this data structure
print $encoder->encode($dirs);

sub parse_dir {
    my $path = shift;    # the dir we're working on

    # get all sub directories (similar to above opendir/readdir calls)
    opendir(my $dh, $path) or die "can't opendir $path: $!";
    my @dirs = grep { ! /^[.]{1,2}/ && -d "$path/$_" } readdir($dh);
    closedir($dh);

    return undef if !scalar @dirs; # nothing to do here, directory empty

    my $vals = [];                            # set our result to an empty array
    foreach my $dir (@dirs) {                 # loop the sub directories         
        my $res = parse_dir("$path/$dir");    # recurse down each path and get results

        # does the returned value have a result, and is that result an array of at 
        # least one element, then add these results to our $vals anonymous array 
        # wrapped in a anonymous hash
        # ELSE
        # push just the name of that directory our $vals anonymous array
        push(@$vals, (defined $res and scalar @$res) ? { $dir => $res } : $dir);
    }

    return $vals;  # return the recursed result
}

E, em seguida, executando o script na estrutura de diretórios proposta ...

./tree2json2.pl .
{
   "dir2" : [
      "dirB",
      "dirA"
   ],
   "dir1" : [
      "dirB",
      {
         "dirA" : [
            "dirBB",
            "dirAA"
         ]
      }
   ]
}

Achei isso muito complicado de acertar (especialmente considerando a lógica "hash if subdiretórios, array caso contrário, OH, A menos que o nível superior, e depois use hashes de qualquer maneira"). Então, eu ficaria surpreso se isso fosse algo que você pudesse fazer com sed/ awk... mas então Stephane ainda não olhou para isso, aposto :)

Drav Sloan
fonte
Oh, o formato para sub-diretórios é um pouco diferente agora, o formato de saída acima será um problema?
Drav Sloan
Sim, eu mesmo giro esse formato. Não tenho certeza de que seja padrão de alguma forma, não consigo encontrar muita coisa disponível que forneça dessa forma, mas sua abordagem é uma melhoria definitiva.
slm
Fazendo algum progresso com isso? 8-)
slm
Fiquei de lado com um slm-style-ascii-network-a-grama em outra questão (pit stop, pois esse estava fazendo minha cabeça girar). Vou fazer uma xícara para corrigir minha relação cafeína / sangue e dar outra olhada.
Drav Sloan
o asciio é a ferramenta para criar em
slm
13

A versão 1.7 inclui suporte para JSON:
http://mama.indstate.edu/users/ice/tree/changes.html

Pela manpágina (abaixo XML/JSON/HTML OPTIONS):

-J     Turn on JSON output. Outputs the directory tree as an JSON formatted array.

por exemplo

$ tree -J                                                                                                 

/home/me/trash/tree-1.7.0
[{"type":"directory","name": ".","contents":[
    {"type":"file","name":"CHANGES"},
    {"type":"file","name":"color.c"},
    {"type":"file","name":"color.o"},
    {"type":"directory","name":"doc","contents":[
      {"type":"file","name":"tree.1"},
      {"type":"file","name":"tree.1.fr"},
      {"type":"file","name":"xml.dtd"}
    ]},
    {"type":"file","name":"hash.c"},
    {"type":"file","name":"hash.o"},
    {"type":"file","name":"html.c"},
    {"type":"file","name":"html.o"},
    {"type":"file","name":"INSTALL"},
    {"type":"file","name":"json.c"},
    {"type":"file","name":"json.o"},
    {"type":"file","name":"LICENSE"},
    {"type":"file","name":"Makefile"},
    {"type":"file","name":"README"},
    {"type":"file","name":"strverscmp.c"},
    {"type":"file","name":"TODO"},
    {"type":"file","name":"tree"},
    {"type":"file","name":"tree.c"},
    {"type":"file","name":"tree.h"},
    {"type":"file","name":"tree.o"},
    {"type":"file","name":"unix.c"},
    {"type":"file","name":"unix.o"},
    {"type":"file","name":"xml.c"},
    {"type":"file","name":"xml.o"}
  ]},
  {"type":"report","directories":1,"files":26}
]
Sridhar Sarnobat
fonte
5

Aqui está uma maneira de usar o Perl e o módulo JSON perl.

$ tree | perl -e 'use JSON; @in=grep(s/\n$//, <>); \
     print encode_json(\@in)."\n";'

Exemplo

Crie alguns dados de amostra.

$ mkdir -p dir{1..5}/dir{A,B}

Aqui está o que parece:

$ tree 
.
|-- dir1
|   |-- dirA
|   `-- dirB
|-- dir2
|   |-- dirA
|   `-- dirB
|-- dir3
|   |-- dirA
|   `-- dirB
|-- dir4
|   |-- dirA
|   `-- dirB
`-- dir5
    |-- dirA
    `-- dirB

15 directories, 0 files

Aqui está uma corrida usando o comando Perl:

$ tree | perl -e 'use JSON; @in=grep(s/\n$//, <>); print encode_json(\@in)."\n";'

O que retorna esta saída:

[".","|-- dir1","|   |-- dirA","|   `-- dirB","|-- dir2","|   |-- dirA","|   `-- dirB","|-- dir3","|   |-- dirA","|   `-- dirB","|-- dir4","|   |-- dirA","|   `-- dirB","`-- dir5","    |-- dirA","    `-- dirB","","15 directories, 0 files"]

NOTA: Este é apenas um encapsulamento da saída de tree. Não é uma hierarquia aninhada. O OP mudou a questão depois que eu sugeri isso!

slm
fonte
desculpe, acho que não descrevi meu problema o suficiente. Meu objetivo é converter algo como: | - dir1 | | - dirA | | - dirB | - dir2 | | - dirA | | - dirB para: {"dir1": ["dirA", "dirB"], "dir2": ["dirA", "dirB"]}
roundrobin 10/13
@BausTheBig - não há problema. Edite sua resposta e adicione um exemplo do que deseja.
slm
A estrutura de dados que o OP parece buscar parece um objeto Python. Eu quase não tenho conhecimento de Python, então não posso ajudar, mas acho que esse tipo de estrutura é mais fácil de construir lá.
terdon
@terdon - eu deixei para Drav, parecia uma estrutura de Hash of Hashes para nós.
slm
2

Eu também estava procurando uma maneira de gerar uma pasta / árvore de arquivos linux para algum arquivo JSON ou XML. Por que não usar este comando simples do terminal:

tree --dirsfirst --noreport -n -X -i -s -D -f -o my.xml

Portanto, apenas o treecomando Linux e configure seus próprios parâmetros. Aqui -Xfornece saída XML. Para mim, tudo bem, e acho que há algum script para converter XML em JSON.

Roelof Berkepeis
fonte
1

Você pode tentar este comando:

tree -a -J -o *filename*

Substitua o nome do arquivo pelo nome do arquivo de saída desejado.


fonte
Não existe esse sinalizador Jpara o comando tree!!
Upvote: na árvore v1.7.0 há uma bandeira J ... cheers
drl
0

Isso faz o trabalho. https://gist.github.com/debodirno/18a21df0511775c19de8d7ccbc99cb72

import os
import sys
import json

def tree_path_json(path):
    dir_structure = {}
    base_name = os.path.basename(os.path.realpath(path))
    if os.path.isdir(path):
        dir_structure[base_name] = [ tree_path_json(os.path.join(path, file_name))\
         for file_name in os.listdir(path) ]
    else:
        return os.path.basename(path)
    return dir_structure

if len(sys.argv) > 1:
    path = sys.argv[1]
else:
    path = '.'

print json.dumps(tree_path_json(path), indent = 4, separators = (', ', ' : '))
Debodirno Chandra
fonte
Eu não entendi
Pierre.Vriens 22/10
Portanto, isso converte a estrutura da árvore em json. Execute esse código em um diretório e ele produzirá o json conforme indicado na pergunta.
Debodirno Chandra