Imprime o nome da subpasta e o conteúdo de result.txt para .csv

6

Eu tenho uma pasta que tem várias subpastas e subpastas. Quero imprimir o conteúdo de um arquivo chamado result.txt , que está presente em muitas subpastas ou subpastas em um arquivo csv junto com o nome da subpasta.

Isso significa que os arquivos com o nome result.txt estão em

abc/def/result.txt
efg/result.txt

Então eu preciso de um arquivo csv que deve ter

1. abc   content of its result.txt
2. efg    content of its result.txt

e assim por diante.

Eu comecei com o seguinte comando find

find . -iname 'result.txt' "a portion of path" "content">final.csv

Como devo proceder daqui?

Observação: (8 de dezembro de 2017) Embora as soluções abaixo exibam o conteúdo adequadamente no terminal, nenhuma delas funciona quando eu adiciono > final.csv. Como já mencionado, o meu result.txt tem mutilines. O conteúdo de um determinado resultado.txt é derramado em células diferentes, em vez de estar em uma única célula. Alguma sugestão?

    
por user8109 07.12.2017 / 19:54

4 respostas

8

Acho que find é a escolha certa:

find */ -name "result.txt" -exec bash -c 'printf "%s,%s\n" "${0%%/*}" "$(cat $0)"' {} \;

Exemplo de execução

$ echo r1 >a/b/result.txt
$ echo r2 >c/result.txt
$ tree
.
├── a
│   └── b
│       └── result.txt
└── c
    └── result.txt
$ find */ -name "result.txt" -exec bash -c 'printf "%s,%s\n" "${0%%/*}" "$(cat $0)"' {} \;
a,r1
c,r2

Explicações

Este comando find pesquisa cada arquivo dentro ou sob o diretório atual do nome result.txt e exec utes o comando printf em um subshell bash . O comando printf imprime o nome do subdiretório, uma vírgula e o conteúdo do arquivo seguido por um \n ewline. Se você quiser gravar essa saída em um arquivo, basta anexar, e. >final.csv para o comando.

Ainda mais simples

é a abordagem -printf sugerida por steeldriver :

$ find */ -name 'result.txt' -printf '%H,' -exec cat {} \;
a/,r1
c/,r2

Isso imprime uma barra adicional na primeira coluna que você pode remover facilmente canalizando a saída por meio de sed 's|/,|,|' .

Mesclando o conteúdo do multilinha result.txt em uma célula

Para substituir caracteres de nova linha por espaços apenas substituem cat por sed ":a;N;\$!ba;s/\n/ /g" em um dos comandos acima, por exemplo,

$ find */ -name "result.txt" -exec bash -c 'printf "%s,%s\n" "${0%%/*}" "$(sed ":a;N;\$!ba;s/\n/ /g" $0)"' {} \;
a,r1 r1
c,r2

Se você quiser alguma outra string como o delimitador, substitua a parte / / por /your_delimiter/ , mas mantenha as barras.

    
por dessert 07.12.2017 / 20:24
5

Bem, aqui está uma maneira (agora editada para transformar as quebras de linha em espaços, graças a esta resposta no Stack Overflow ) :

shopt -s globstar
n=0; for i in **/result.txt; do sed -e ":l;N;\$!bl;s/\n/ /g; s/.*/$((++n))\. "${i%%/*}"\t&/" "$i"; done

Você pode adicionar um redirecionamento para gravar em um arquivo

n=0; for i in **/result.txt; do sed ":l;N;\$!bl;s/\n/ /g; s/.*/$((++n))\. "${i%%/*}"\t&/" "$i"; done > outfile

Notas

  • n=0 definiu uma variável para incrementar
  • shopt -s globstar Ative a globalização recursiva com ** para localizar todos os arquivos em diretórios abaixo desse (depois, desmarque shopt -u globstar ou saia do shell e inicie um novo)
  • :l definiu um rótulo para esta ação
  • N leu duas linhas no espaço de padrão (isso nos permite usar \n )
  • \$! não se esta for a última linha do arquivo ... nós temos que escapar $ porque o comando inteiro é entre aspas duplas para que o shell possa expandir $i etc. Mas esse $ precisa ser passado intacto para sed , onde significa" a última linha do arquivo ". Recomendo usar aspas simples para sed scripts, a menos que você tenha que passar pelo shell variáveis nelas.
  • bl ... ramificar para rotular (faça isso de novo)
  • s/old/new replace old com new
  • s/\n/ /g para todos os caracteres de nova linha no espaço de padrão (todos menos o último), substitua a nova linha por um espaço
  • .* qualquer número de qualquer caractere (qualquer coisa no arquivo)
  • $((++n)) increment n a cada iteração do loop
  • \. literal dot (vírgulas não são tratadas especialmente por sed ; elas serão impressas literalmente)
  • "${i%%/*}" o nome do primeiro subdiretório do atual no caminho do arquivo com o qual estamos lidando (tira todos os caracteres após o primeiro / )
  • & o padrão correspondente da seção de pesquisa (qualquer coisa no arquivo)
  • -- não interpreta a entrelinha - nos argumentos subsequentes como prepending flags de opção. Isso impede que os nomes de arquivos que começam com - sejam interpretados como opções. Isso é desnecessário neste caso específico, porque estamos pesquisando explicitamente por result.txt e somente arquivos com esse nome exato serão passados para o loop. No entanto, eu incluí-lo, no caso de alguém precisar reutilizar este script com um glob.

Aqui está uma versão mais legível, que também é mais portável (deve funcionar em todas as versões de sed ), já que usa novas linhas em vez de ; para separar comandos:

#!/bin/bash

shopt -s globstar
n=0
for i in **/result.txt; do
         sed ":l      
              N        
              \$!bl     
              s/\n/ /g
              s/.*/$((++n))\.,"${i%%/*}",&/" -- "$i"
done > outfile
    
por Zanna 07.12.2017 / 20:12
2

Solução de script bash

#!/bin/bash
# If $1 is not given, find will assume cwd
print_file(){
    local inputfile="$1"
    while IFS= read -r line || [ -n "$line" ];do
        printf "%s\" "$line"
    done < "$inputfile"
}

get_file_info(){
    local filepath="$1"
    counter=$((counter+1))
    parent=${filepath%/*}
    if [ "$parent" = "$filepath"  ]; then
        parent="."
    fi
    printf "%d,%s," "$counter" "$parent"
}

main(){
    if [ -z "$1"  ];then
        set "."
    fi

    find "$1" -type f -name "result.txt" -print0 |
    while IFS= read -r -d ''  path
    do
        get_file_info "$path"
        print_file "$path"
        printf "\n"
    done
}

main "$@"

A maneira como isso funciona é que você deve salvá-lo como arquivo, por exemplo results2csv.sh , tornar executável com chmod +x e executar o caminho completo para o script ou colocá-lo na pasta ~/bin , executar source ~/.bashrc e chame o script pelo nome.

Veja como esse script funciona:

$ ./result2csv.sh things                                                    
1,things/thing2,to be or not to be\that's Boolean logic\
2,things/thing1,one potato\two potato\

Dê ao script o diretório mais superior e ele passará pelos subdiretórios que localizam os arquivos e geram o caminho para o arquivo de acordo com a maneira como você especificou o início da maioria dos diretórios. Portanto, por exemplo, se você especificou ./things como o máximo, isso resultaria na primeira linha tendo ./thing/things2 como caminho para o arquivo. Novas linhas são substituídas por barras invertidas para mostrar o conteúdo do arquivo. Observe que ele também assumirá o diretório de trabalho atual "." se o diretório não estiver especificado.

$ cd things
$ ../result2csv.sh                                                          
1,./thing2,to be or not to be\that's Boolean logic\
2,./thing1,one potato\two potato\

Tudo o que você precisa fazer agora é chamar results2csv.sh directory > output.csv para enviar dados para um arquivo e pronto

    
por Sergiy Kolodyazhnyy 08.12.2017 / 20:03
-1

Eu não sei exatamente como fazer isso com apenas comandos de terminal, mas fiz algo similar usando o script python deste thread:

link

Com isso, você pode facilmente adicionar funcionalidades para escrever linhas no arquivo CSV:

link para o Python 2

link para o Python 3

    
por marekbrze 07.12.2017 / 20:11