2
votes

Comment trier les fichiers dans la commande Coller avec 500 fichiers CSV

Ma question est similaire à Comment trier les fichiers dans la commande coller ? - qui a été résolu.

J'ai 500 fichiers csv (données de précipitations quotidiennes) dans un dossier avec la convention de dénomination chirps_yyyymmdd.csv . Chaque fichier n'a qu'une seule colonne (valeur des précipitations) avec 100 000 lignes et aucun en-tête. Je souhaite fusionner tous les fichiers csv en un seul csv par ordre chronologique.

Quand j'ai essayé ce script ls -v fichier _ *. csv | xargs coller -d, avec seulement 100 fichiers csv, cela a fonctionné. Mais lorsque j'ai essayé d'utiliser 500 fichiers csv, j'ai eu cette erreur: paste: chirps_19890911.csv: Trop de fichiers ouverts

Comment gérer l'erreur ci-dessus?

Pour une solution rapide, je peux diviser les csv en deux dossiers et effectuer le processus en utilisant le script ci-dessus. Mais, le problème, j'ai 100 dossiers et il a 500 csv dans chaque dossier.

Merci

Exemple de données et résultat attendu: https: //www.dropbox .com / s / ndofxuunc1sm292 / data.zip? dl = 0


7 commentaires

Bienvenue dans StackOverflow! Il me semble que votre question ne concerne pas sed, awk ou csv, mais comment utiliser le shell. Si ce n'est pas vraiment une question de programmation, c'est probablement hors sujet pour StackOverflow. Vous pouvez envisager de le fermer ici et de publier une version révisée de votre question sur SuperUser.com ou unix.stackexchange.com . Cherchez également à utiliser une boucle for dans votre shell. C'est la manière canonique de traiter les fichiers de manière séquentielle. L'analyse ls n'est généralement pas une bonne idée. .


Salut @ghoti merci pour votre réponse, j'ai révisé la question et supprimer la balise inutile


Quel est le nom de vos dossiers?


@Cyrus Le nom du dossier est une année, de 1900 à 2018


Les bonnes questions ont souvent un exemple minimal, complet et vérifiable . Pouvez-vous nous montrer quelques exemples d'entrée et de sortie? Si une solution de coller ne fonctionne pas, autre chose pourrait le faire, mais il serait bon de savoir que nous sommes sur la bonne voie en étant capable de reproduire des résultats positifs.


J'ai révisé ma question, et mis le lien par exemple les données et la sortie attendue


Collez les 500 fichiers de chaque répertoire dans son propre fichier, puis collez ces 100 nouveaux fichiers dans un seul fichier volumineux.


5 Réponses :


0
votes

Commencez par créer un fichier sans le coller et changez-le en un seul doubleur avec tr:

cat */chirps_*.csv | tr "\n" "," > long.csv


3 commentaires

Merci pour la réponse, mais en utilisant le script ci-dessus, j'ai obtenu le résultat en 1 ligne. Alors que mes données pour chaque fichier sont 1 colonne et 100 000 lignes et sans en-tête. Si je veux fusionner 500 fichiers csv, je dois obtenir le résultat 500 colonnes et 100000 lignes


Dans ce cas, omettez le tr , cat * / chirps _ *. Csv> long.csv devrait fonctionner. Vous avez mentionné coller , c'est pourquoi j'ai essayé de coller les lignes.


Le résultat est comme une transposition du code précédent.



0
votes

Vous pouvez le faire avec gawk comme ceci ...

Il suffit de lire tous les fichiers l'un après l'autre et de les enregistrer dans un tableau. Le tableau est indexé par deux nombres, d'une part le numéro de ligne dans le fichier courant ( FNR ) et d'autre part la colonne, que j'incrémente à chaque fois que nous rencontrons un nouveau fichier dans le code BEGINFILE > block.

Ensuite, à la fin, imprimez le tableau entier:

$HOME/merge > merged.csv

SEP est juste un caractère inutilisé cela fait un séparateur entre les indices. J'utilise gawk car BEGINFILE est utile pour incrémenter le numéro de colonne.


Enregistrez ce qui précède dans votre répertoire HOME en tant que merge . Ensuite, démarrez un Terminal et, une seule fois, rendez-le exécutable avec la commande:

$HOME/merge

Maintenant, changez le répertoire où sont vos chirps avec une commande comme:

cd subdirectory/where/chirps/are

Vous pouvez maintenant exécuter le script avec:

chmod +x merge

La sortie passera rapidement à l'écran. Si vous le souhaitez dans un fichier, utilisez:

gawk 'BEGINFILE{ ++col }                        # New file, increment column number
               { X[FNR SEP col]=$0; rows=FNR }  # Save datum into array X, indexed by current record number and col
      END      { for(r=1;r<=rows;r++){
                    comma=","
                    for(c=1;c<=col;c++){
                       if(c==col)comma=""
                       printf("%s%s",X[r SEP c],comma)
                    }
                    printf("\n")
                 }
               }' chirps*


7 commentaires

Merci, mais je suis désolé de ne pas connaître le code ci-dessus. Comment l'utiliser? Enregistrez-le en tant que fichier de code et exécutez-le dans le terminal? Ou simplement coller et exécuter?


Désolé, j'ai ajouté des notes sur la façon de l'utiliser à la fin. Veuillez dire si vous êtes coincé. Bonne chance.


Merci, j'ai réussi à installer gawk dans mon mac en utilisant "brew install gawk". Et puis en suivant votre ligne directrice, essayez de l'exécuter. C'est déjà 15 minutes, toujours pas encore fini. Ai-je manqué quelque chose?


Cela ne fonctionnera pas très bien si vous n'avez pas beaucoup de mémoire car il charge tous les fichiers par colonne en mémoire puis les imprime par ligne, je ne l'ai testé qu'avec votre échantillon de données et cela a bien fonctionné mais je ne l'ai pas essayez avec plus que cela. Une fois qu'il commence à sortir, ce sera presque instantané.


Ah ok. J'attendrai.


Vous pouvez essayer de vérifier la quantité de mémoire dont vous disposez dans le menu Pomme -> À propos de ce Mac , puis démarrez Activity Monitor et cliquez sur Mémoire en haut pour voyez combien de mémoire cela prend et si cela continue de grandir.


Ou, vous pouvez exécuter à nouveau (si vous avez la patience) et changer le code où il dit ++ col en ++ col; print col pour voir le numéro du fichier en cours de lecture afin que vous ayez une idée de l'endroit où il se trouve.



0
votes

Si l'objectif est un fichier avec 100 000 lignes et 500 colonnes, quelque chose comme ceci devrait fonctionner:

paste -d, chirps_*.csv > chirps_500_merge.csv

Un code supplémentaire peut être utilisé pour trier les chirps _... fichiers d'entrée dans n'importe quel ordre souhaité avant de collering.


0 commentaires

0
votes

L'erreur vient de ulimit , de man ulimit a>:

-n ou --file-descriptor-count Le nombre maximum de descripteurs de fichiers ouverts

Sur mon système, ulimit -n renvoie 1024.

Heureusement, nous pouvons coller la sortie collée, afin de pouvoir l'enchaîner.

func() {
        paste -d, "$@"
}

tmp=()
tmpfilecreated=0
while readarray -t -n1023 files && ((${#files[@]})); do
        tmp=("$(mktemp)")

        func "${tmp[@]}" "${files[@]}" >"$tmp"

        if ((tmpfilecreated)); then
                rm "${files[0]}"
        fi
        tmpfilecreated=1
done

func "${tmp[@]}" "${files[@]}"

if ((tmpfilecreated)); then
        rm "${files[0]}"
fi
  1. Ne pas analyser la sortie ls
  2. Une fois que nous sommes passés de l'analyse de la sortie ls à la bonne recherche, nous trouvons tous les fichiers et les trions.
  3. le premier xargs prend 1024 fichiers à la fois, crée un fichier temporaire, colle la sortie dans un fichier temporaire et génère le nom du fichier temporaire
  4. Le deuxième xargs fait de même avec les fichiers temporaires, mais supprime également tous les temporaires
  5. Comme le nombre de fichiers serait de 100 * 500 = 500000, ce qui est plus petit que 1024 * 1024, nous pouvons nous en tirer en un seul passage.
  6. Testé par rapport aux données de test générées avec:

    func() {
            paste -d, "$@"
    }
    
    files=()
    tmpfilecreated=0
    
    # read filenames...c
    while IFS= read -r line; do
    
            files+=("$line")
    
            # if the limit of 1024 files is reached
            if ((${#files[@]} == 1024)); then
                    tmp=$(mktemp)
    
                    func "${files[@]}" >"$tmp"
    
                    # remove the last tmp file
                    if ((tmpfilecreated)); then
                            rm "${files[0]}"
                    fi
                    tmpfilecreated=1
    
                    # start with fresh files list
                    # with only the tmp file
                    files=("$tmp")
            fi
    done
    
    func "${files[@]}"
    
    # remember to clear tmp file!
    if ((tmpfilecreated)); then
            rm "${files[0]}"
    fi
    
  7. Le problème semble être un peu comme foldl avec une taille maximale de morceau à plier en une seule passe. Fondamentalement, nous voulons que coller -d, ) ) qui s'exécute de manière récursive. Avec un peu de plaisir, j'ai trouvé ce qui suit:

seq 1 2000 |
xargs -P0 -n1 -t sh -c '
    seq 1 1000 |
    sed "s/^/ $RANDOM/" \
    >"file_$(date --date="-${1}days" +%Y%m%d).csv"
' --

Je suppose que readarray / mapfile pourrait être plus rapide, et en résulter un peu code plus clair:

find . -type f -name 'file_*.csv' | 
sort | 
xargs -n$(ulimit -n) sh -c '
     tmp=$(mktemp); 
     paste -d, "$@" >$tmp; 
     echo $tmp
' -- |
xargs sh -c '
     paste -d, "$@"
     rm "$@"
' --

PS. Je souhaite fusionner tous les fichiers csv en un seul csv par ordre chronologique. Ne serait-ce pas simplement couper ? À l'heure actuelle, chaque colonne représente un jour.


6 commentaires

Merci pour le code. Comment l'utiliser? Enregistrez-le en tant que fichier de code et exécutez-le dans le terminal? Ou simplement coller et exécuter?


Le premier extrait de code est prêt pour la copie. Pour le reste - find. -type f -name 'fichier * .csv' | trier | (....) - Remplacez le ... par le code 3ème ou 4ème extrait de code.


Pardon de mon ignorance, je ne suis pas familier avec cela. J'ai entré tout le code. Où est l'emplacement de sortie (tmp)? Est-ce dans $ HOME?


Il y a un reste de mes tests -p / tmp / a dans l'argument mktemp à l'intérieur de xargs, devrait probablement être supprimé.


Cela a fonctionné, maintenant toujours en cours d'exécution et je peux voir la progression dans Terminal. Mais comment enregistrer la sortie dans un fichier? Disons merged.csv dans le même dossier avec les données d'entrée.


Utilisez la redirection de commande blabal | blabla | blabla | blabla> fichier bash enregistrer la sortie dans un fichier . Vous pouvez également | fichier tee pour avoir la sortie à la fois dans le fichier et sur stdout



0
votes

Vous pouvez essayer cette doublure Perl-one. Cela fonctionnera pour n'importe quel nombre de fichiers correspondant à * .csv sous un répertoire

$ ls -1 *csv
file_1.csv
file_2.csv
file_3.csv
$ cat file_1.csv
1
2
3
$ cat file_2.csv
4
5
6
$ cat file_3.csv
7
8
9

$ perl -e  ' BEGIN { while($f=glob("*.csv")) { $i=0;open($FH,"<$f"); while(<$FH>){ chomp;@t=@{$kv{$i}}; push(@t,$_);$kv{$i++}=[@t];}} print join(",",@{$kv{$_}})."\n" for(0..$i) } '                                                                              <
1,4,7
2,5,8
3,6,9

$


0 commentaires