J'essaie un naïf:
$ cat * | sort -u > /tmp/bla.txt
qui échoue avec:
-bash: /bin/cat: Argument list too long
Donc, pour éviter une solution idiote comme (crée un énorme fichier temporaire):
$ find . -type f -exec cat {} >> /tmp/unsorted.txt \;
$ cat /tmp/unsorted.txt | sort -u > /tmp/bla.txt
Je pensais que je pouvais traiter les fichiers un par un en utilisant (cela devrait réduire la consommation de mémoire et être plus proche d'un mécanisme de streaming):
$ cat proc.sh
#!/bin/sh
old=/tmp/old.txt
tmp=/tmp/tmp.txt
cat $old "$1" | sort -u > $tmp
mv $tmp $old
Suivi ensuite par:
$ touch /tmp/old.txt
$ find . -type f -exec /tmp/proc.sh {} \;
Existe-t-il un remplacement plus simple de style Unix pour: cat * | sort -u
lorsque le nombre de fichiers atteint MAX_ARG
? Il est difficile d'écrire un petit script shell pour une tâche aussi courante.
sort
le fait automatiquement pour plusieurs entrées de fichiers .. maissort -u *
échoueraitArgument list too long
aussi avec je supposeRéponses:
Avec GNU
sort
, et un shell oùprintf
est intégré (tous ceux de type POSIX de nos jours sauf quelques variantes depdksh
):Maintenant, un problème avec cela est que parce que les deux composants de ce pipeline sont exécutés simultanément et indépendamment, au moment où celui de gauche développe le
*
glob, le droit peut avoiroutput
déjà créé le fichier, ce qui pourrait poser problème (peut-être pas avec-u
ici) comme ceoutput
serait à la fois un fichier d'entrée et de sortie, vous voudrez peut-être que la sortie aille dans un autre répertoire (> ../output
par exemple), ou assurez-vous que le glob ne correspond pas au fichier de sortie.Une autre façon de le résoudre dans ce cas est de l'écrire:
De cette façon, il
sort
s'ouvreoutput
pour l'écriture et (dans mes tests), il ne le fera pas avant d'avoir reçu la liste complète des fichiers (tant de temps après que le glob ait été développé). Cela évitera également de vous encombreroutput
si aucun des fichiers d'entrée n'est lisible.Une autre façon de l'écrire avec
zsh
oubash
Cela utilise la substitution de processus (où
<(...)
est remplacé par un chemin de fichier qui se réfère à la fin de lecture du canal d'printf
écriture). Cette fonctionnalité vient deksh
, maisksh
insiste pour rendre l'expansion d'<(...)
un argument séparé à la commande afin que vous ne puissiez pas l'utiliser avec la--option=<(...)
syntaxe. Cela fonctionnerait cependant avec cette syntaxe:Notez que vous verrez une différence avec les approches qui alimentent la sortie de
cat
sur les fichiers dans les cas où il y a des fichiers qui ne se terminent pas par un caractère de nouvelle ligne:Notez également que
sort
trie en utilisant l'algorithme de classement dans les paramètres régionaux (strcollate()
), etsort -u
signale une de chaque ensemble de lignes qui trient la même chose par cet algorithme, pas des lignes uniques au niveau de l'octet. Si vous ne vous souciez que des lignes uniques au niveau de l'octet et ne vous souciez pas tellement de l'ordre dans lequel elles sont triées, vous souhaiterez peut-être fixer les paramètres régionaux à C où le tri est basé sur les valeurs d'octets (memcmp()
; cela accélérerait probablement les choses de manière significative):la source
sort
d'optimiser sa consommation de mémoire. Je trouve quand mêmeprintf '%s\0' *
un peu complexe à taper.find . -type f -maxdepth 1 -print0
place deprintf '%s\0' *
, mais je ne peux pas affirmer que c'est plus facile à taper. Et ce dernier est plus facile à définir comme un alias, bien sûr!echo
a un-n
, j'aurais préféré quelque chose commeprintf -0 %s
ça semble un niveau un peu moins bas que'%s\0'
-maxdepth
et-print0
sont des extensions GNU (bien que largement prises en charge de nos jours). Avec d'autresfind
s (bien que si vous avez un tri GNU, vous aurez probablement aussi GNU find), vous pouvez le faireLC_ALL=C find . ! -name . -prune -type f ! -name '.*' -exec printf '%s\0' {} +
(LC_ALL=C
pour exclure toujours les fichiers cachés qui contiennent des caractères invalides, même avec GNUfind
), mais c'est un peu exagéré quand vous en général ontprintf
intégré.print0
fonction commeprint0() { [ "$#" -eq 0 ] || printf '%s\0' "$@";}
puisprint0 * | sort...
Un correctif simple, fonctionne au moins dans Bash, car il
printf
est intégré, et les limites des arguments de la ligne de commande ne s'appliquent pas:(
echo * | xargs
fonctionnerait également, sauf pour la gestion des noms de fichiers avec des espaces blancs, etc.)la source
cat
processus distinct pour chaque fichier.find -exec {} +
regroupe plusieurs fichiers par exécution. Avecfind -exec \;
ce serait un chat par fichier.Cela va concaténer tous les fichiers réguliers non cachés dans le répertoire courant et trier leur contenu combiné (tout en supprimant les lignes dupliquées) dans le fichier
/path/to/sorted.txt
.la source
|
enchaînera correctement les opérations pour limiter l'utilisation de la mémoire?sort
effectuera un tri hors cœur si les besoins en mémoire l'exigent. Le côté gauche du pipeline consommera très peu de mémoire en comparaison.L'efficacité est un terme relatif, vous devez donc vraiment spécifier quel facteur vous souhaitez minimiser; cpu, mémoire, disque, temps, etc. Pour les besoins de l'argument, je vais supposer que vous vouliez minimiser l'utilisation de la mémoire et êtes prêt à passer plus de cycles de cpu pour y parvenir. Des solutions comme celle de Stéphane Chazelas fonctionnent bien
mais ils supposent que les fichiers texte individuels ont un haut degré d'unicité pour commencer. Si ce n'est pas le cas, c'est-à-dire si après
sample.srt est plus de 10% plus petit que sample.txt, vous économiserez alors de la mémoire en supprimant les doublons dans les fichiers avant de fusionner. Vous économiserez également davantage de mémoire en ne chaînant pas les commandes, ce qui signifie que les résultats de différents processus n'ont pas besoin d'être en mémoire en même temps.
la source
sort
lesort
recours à des fichiers temporaires lorsque l'utilisation de la mémoire dépasse un seuil (généralement relativement faible).base64 /dev/urandom | sort -u
remplira votre disque mais n'utilisera pas beaucoup de mémoire.sort
implémentations, y compris celle d'origine dans Unix v3 en 1972, mais apparemment pas debusybox sort
. Vraisemblablement parce que celui-ci est destiné à fonctionner sur de petits systèmes qui n'ont pas de stockage permanent.yes | sort -u
(toutes les données dupliquées) ne doivent pas utiliser plus de quelques octets de mémoire sans parler du disque. Mais avec GNU et Solarissort
au moins, nous le voyons écrire de gros fichiers de 2 octets/tmp
(y\n
pour chaque mégaoctet d'entrée), il finira donc par remplir le disque.Comme @ilkkachu, mais le chat (1) n'est pas nécessaire:
De plus, si les données sont si longues, vous voudrez peut-être utiliser l'option sort (1) --parallel = N
Lorsque N est le nombre de CPU que votre ordinateur possède
la source