web-dev-qa-db-fra.com

Imprimer le nom du sous-dossier et le contenu de result.txt en .csv

J'ai un dossier qui a plusieurs sous-dossiers et sous-dossiers. Je souhaite imprimer le contenu d'un fichier nommé result.txt, présent dans de nombreux sous-dossiers ou sous-dossiers dans un fichier CSV accompagné du nom du sous-dossier.

Cela signifie que si les fichiers nommés result.txt sont dans

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

Ensuite, j'ai besoin d'un fichier csv qui devrait avoir

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

etc.

J'ai commencé avec la commande find suivante

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

Comment dois-je procéder à partir d'ici?

Remarque: (8 décembre 2017) Bien que les solutions ci-dessous affichent le contenu correctement sur le terminal, aucune d'entre elles ne fonctionne lorsque j'ajoute> final.csv. Comme déjà mentionné, mon résultat.txt contient des mutilines. Le contenu de un résultat en particulier est renvoyé dans différentes cellules plutôt que dans une seule cellule. Des suggestions?

6
user8109

Je pense que find est le bon choix:

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

Exemple d'exécution

$ 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

Des explications

Cette commande find recherche dans tous les fichiers situés dans ou sous le répertoire en cours du nom result.txt et exec active la commande printf dans un sous-shell bash. La commande printf affiche le nom du sous-répertoire, une virgule et le contenu du fichier suivi d'un \newline. Si vous souhaitez écrire cette sortie dans un fichier, ajoutez simplement, par exemple, >final.csv à la commande.

Encore plus simple

est l’approche -printf suggérée par steeldriver :

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

Ceci imprime une barre oblique supplémentaire dans la première colonne que vous pouvez facilement supprimer en dirigeant la sortie, par ex. sed 's|/,|,|'.

Fusion de plusieurs lignes result.txt contenu dans une cellule

Pour remplacer les caractères de nouvelle ligne par ex. les espaces ne font que remplacer cat par sed ":a;N;\$!ba;s/\n/ /g" dans l'une des commandes ci-dessus, par exemple.

$ 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

Si vous souhaitez utiliser une autre chaîne comme délimiteur, remplacez la partie / / par /your_delimiter/, mais conservez les barres obliques.

8
dessert

Eh bien, voici un moyen (maintenant modifié pour transformer les sauts de ligne en espaces, grâce à cette réponse sur 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

Vous pouvez ajouter une redirection pour écrire dans un fichier

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

Remarques

  • n=0 définir une variable à incrémenter
  • shopt -s globstar Activez la suppression récursive avec ** pour rechercher tous les fichiers dans les répertoires situés en dessous de celui-ci (désélectionnez-le avec shopt -u globstar, ou quittez le shell et démarrez-en un nouveau).
  • :l définir une étiquette pour cette action
  • N lit deux lignes dans l'espace du modèle (cela nous permet d'utiliser \n)
  • \$! pas s'il s'agit de la dernière ligne du fichier ... nous devons échapper à $ car la commande entière est double guillemet pour que le shell puisse se développer $i etc. Mais ce $ doit être transmis intact à sed, où il signifie "la dernière ligne du fichier". Je recommande d'utiliser guillemets simples pour sed les scripts, sauf si vous devez y passer des variables Shell.
  • bl ... branche à l'étiquette (recommencez)
  • s/old/new remplace old par new
  • s/\n/ /g pour tous les caractères de nouvelle ligne de l'espace modèle (tous sauf le dernier), remplacez la nouvelle ligne par un espace
  • .* un nombre quelconque de caractères (quelque chose dans le fichier)
  • $((++n)) incrémenter n à chaque itération de la boucle
  • \. point littéral (les virgules ne sont pas traitées spécialement par sed; elles seront imprimées littéralement)
  • "${i%%/*}" le nom du premier sous-répertoire du répertoire actuel dans le chemin du fichier que nous traitons (effacez tous les caractères après le premier /)
  • & le motif correspondant dans la section de recherche (tout ce qui est dans le fichier)
  • -- n'interprète pas - dans les arguments suivants en tant qu'indicateurs d'options. Cela évite que les noms de fichiers commençant par - soient interprétés comme des options. Ceci est inutile dans ce cas particulier, car nous recherchons explicitement result.txt et seuls les fichiers portant ce nom exact seront transmis à la boucle. Cependant, je l'ai inclus, au cas où quelqu'un aurait besoin de réutiliser ce script avec un glob.

Voici une version plus lisible, qui est également plus portable (devrait fonctionner dans toutes les versions de sed) car elle utilise des nouvelles lignes au lieu de ; pour séparer les commandes:

#!/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
5
Zanna

Solution 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 "$@"

La façon dont cela fonctionne est que vous devriez enregistrer ceci en tant que fichier, par exemple results2csv.sh, rendre exécutable avec chmod +x et l'exécuter en donnant le chemin complet du script ou en le plaçant dans le dossier ~/bin, lancez source ~/.bashrc et appelez le script par son nom.

Voici comment ce script fonctionne:

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

Donnez au script le répertoire le plus haut, et il passera par les sous-répertoires pour trouver les fichiers et affichera le chemin d'accès au fichier conformément à la façon dont vous avez spécifié le répertoire le plus haut. Ainsi, par exemple, si vous spécifiez ./things en haut de la liste, la première ligne aurait pour résultat ./thing/things2 comme chemin d'accès au fichier. Les nouvelles lignes sont remplacées par des barres obliques inverses pour afficher le contenu du fichier. Notez que cela supposera également le répertoire de travail actuel "." si le répertoire n'est pas spécifié.

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

Il ne vous reste plus qu'à appeler results2csv.sh directory > output.csv pour exporter les données dans un fichier, et vous avez terminé.

2