Comment combiner la commande 'tar' avec 'find'

31

La commande find donne cette sortie:

[root @ localhost /] # find var / log / -iname anaconda. *
var / log / anaconda.log
var / log / anaconda.xlog
var / log / anaconda.yum.log
var / log / anaconda.syslog
var / log / anaconda.program.log
var / log / anaconda.storage.log

Après avoir combiné avec tar, il affiche cette sortie:

[root @ localhost /] # find var / log / -iname anaconda. * -exec tar -cvf file.tar {} \;
var / log / anaconda.log
var / log / anaconda.xlog
var / log / anaconda.yum.log
var / log / anaconda.syslog
var / log / anaconda.program.log
var / log / anaconda.storage.log

Mais tout en listant le fichier tar, il n'affiche qu'un seul fichier

[root @ localhost /] # tar -tvf file.tar
-rw ------- root / root 208454 2012-02-27 12:01 var / log / anaconda.storage.log

Qu'est-ce que je fais mal ici?

Avec xargs, j'obtiens cette sortie:

[root @ localhost /] # find var / log / -iname anaconda. * | xargs tar -cvf file1.tar

Deuxième question

Tout en tapant / devant var, signifie find /var/logpourquoi il donne ce tar mesaage : Suppression du `/ 'en tête des noms de membres

[root @ localhost /] # find / var / log / -iname anaconda. * -exec tar -cvf file.tar {} \;
tar: Suppression des `/ 'en tête des noms de membres
/var/log/anaconda.log
tar: Suppression des `/ 'en tête des noms de membres
/var/log/anaconda.xlog
tar: Suppression des `/ 'en tête des noms de membres
/var/log/anaconda.yum.log
tar: Suppression des `/ 'en tête des noms de membres
/var/log/anaconda.syslog
tar: Suppression des `/ 'en tête des noms de membres
/var/log/anaconda.program.log
tar: Suppression des `/ 'en tête des noms de membres
/var/log/anaconda.storage.log

Sous une forme simple, quelle est la différence entre les deux suivants?

find var/log et find /var/log

max
la source
C'est un sujet semi + off, mais pour aller de l'avant avec la findcommande, vous devez citer le terme de recherche. Cela fonctionne sans parfois mais pas toujours.
nerdwaller
1
Si vous utilisez {} +au lieu de {} \;cela, les résultats de la recherche seront regroupés en un seul argument
Jason S

Réponses:

39

Remarque: Voir la réponse de @ Iain pour une solution un peu plus efficace.

Notez que findcela appellera l' -execaction pour chaque fichier qu'il trouve.

Si vous exécutez tar -cvf file.tar {}pour chaque findsortie de fichier unique , cela signifie que vous écraserez à file.tarchaque fois, ce qui explique pourquoi vous vous retrouvez avec une seule archive qui ne contient que anaconda.storage.log- ce sont les dernières findsorties de fichier .

Maintenant, vous voulez réellement ajouter les fichiers à l'archive au lieu de les créer à chaque fois (c'est ce que fait l' -coption). Alors, utilisez ce qui suit:

find var/log/ -iname "anaconda.*" -exec tar -rvf file.tar {} \;

L' -roption s'ajoute à l'archive au lieu de la recréer à chaque fois.

Remarque: remplacez -iname anaconda.*par -iname "anaconda.*". L'astérisque est un caractère générique et peut être étendu par votre shell avant findmême de le voir. Pour empêcher cette expansion, encapsulez l'argument entre guillemets doubles.


Quant à la tarsuppression de l'interlignage /: l'archive ne doit contenir que des noms de fichiers relatifs . Si vous ajoutez des fichiers avec un interligne /, ils seront stockés sous forme de noms de fichiers absolus , signifiant littéralement /var/…sur votre ordinateur, par exemple.

IIRC c'est simplement une précaution pour les tarimplémentations autres que GNU, et c'est plus sûr de cette façon parce que vous n'écraserez pas vos données réelles /var/…lorsque vous extrayez l'archive si elle contient des noms de fichiers relatifs.

slhck
la source
6
Mais notez que si vous tentiez de tarcréer une archive de bande réelle de cette manière, en ajoutant un fichier à la fois, en rembobinant la bande, puis en relisant le tout à chaque fois pour arriver à la fin, le tout serait ridiculement lent. Votre solution ne convient que si vous écrivez le fichier tar sur le disque.
Nicole Hamilton
2
C'est vrai, mais je pense que nous pouvons ignorer cette situation en toute sécurité;)
slhck
@slhck * est un caractère générique qui devrait correspondre à toutes les possibilités, n'est-ce pas? mais ici find /var/log/ -iname anaconda*ne donnant rien et find /var/log/ -iname anaconda.*donnant la sortie, pourquoi?
max
Lorsqu'un caractère générique est consommé, il ne sera plus vu par find. Donc, si vous en avez anaconda*, et dans votre dossier actuel, il y a quelque chose nommé, par exemple, anaconda5(correspondant à ce caractère générique), le caractère générique sera développé et findverra à la -iname anaconda5place de -iname anaconda*. Pourquoi le premier ne fonctionne pas et le second dépend des fichiers qui se trouvent dans votre répertoire actuel. @max
slhck
2
Vous pouvez utiliser à la {} +place de {} \;donc il regroupera les résultats de find en un seul argument
Jason S
41

Vous pouvez utiliser quelque chose comme:

find var/log -iname 'anaconda.*' -print0 | tar -cvf somefile.tar --null -T -

Le -print0et -Ttravailler ensemble pour autoriser les noms de fichiers avec des espaces de nouvelles lignes, etc. La finale -indique à tar de lire les noms de fichiers d'entrée depuis stdin.

Notez que cela -print0doit venir à la fin de votre déclaration, par cette réponse . Sinon, vous obtiendrez probablement plus de fichiers que prévu.

Peter Mortensen
la source
2
Vous avez omis cette -nameoption, entraînant votre solution dans tarl'ensemble du répertoire. Si c'est ce que vous voulez, vous pouvez le faire plus facilement car tar -cvf file.tar var/logsans utiliser finddu tout.
Nicole Hamilton
2
+1 Piping la liste tarest une bonne idée. C'est certainement la meilleure solution si vous vous attendez à ce que les chemins d'accès puissent avoir des espaces. Je le décrirais même comme le meilleur techniquement, car il est à la fois fiable et efficace. Mais cela nécessite une connaissance particulière supplémentaire des deux findet tar. Je préfère la substitution de commandes à peu près uniquement parce que c'est un outil plus général: apprenez à l'utiliser une fois, puis utilisez-le partout. (Mais je concède, je suis sur Windows avec un shell où cela fonctionne toujours.) Toutes mes excuses si j'avais l'air grossier.
Nicole Hamilton
2
Vous avez déjà obtenu votre +1. Soyez heureux. :) Les longues lignes de commande sont toujours le fléau de la création de processus i / f sur n'importe quel OS. Je me souviens avoir discuté avec Mark Lucovsky chez Microsoft au début des années 90 que leur limite de caractères Unicode 32K sur NT était trop petite et l'avoir plaint, je n'avais aucune idée du nombre d'octets de plus qu'il faudrait pour stocker des longueurs aussi longues plutôt que des courts-circuits partout dans le noyau. . Soupir. Les solutions de cas plus générales lorsque la liste d'arguments est trop longue sont d'en faire plus dans le shell (si possible; dans le mien c'est) ou d'utiliser xargs.
Nicole Hamilton
9
si vous utilisez l' -print0option find , vous avez également besoin de l' --nulloption tar .
mivk
2
Et --no-unquotes'avère également nécessaire: les noms de fichiers contenant des barres obliques inverses seraient sinon mal gérés. (Non, ce n'est pas une hypothèse - je crée vraiment une archive tar à partir du code de quelqu'un d'autre, contenant un nom de fichier avec des barres obliques inversées dans le nom, c'est ainsi que j'ai découvert.)
hvd
12

Essaye ça:

tar -cvf file.tar `find var/log/ -iname "anaconda.*"`

Vous essayez d'utiliser findpour -exec tar. Mais la façon dont l' -execoption fonctionne, il exécute cette commande une fois pour chaque fichier correspondant qu'il trouve, provoquant l' tarécrasement du fichier tar qu'il produit à chaque fois. C'est pourquoi vous ne vous êtes retrouvé qu'avec le dernier. De plus, vous devez mettre des guillemets autour du motif que vous spécifiez pour findque le shell ne le développe pas avant de le passer à find.

En utilisant la substitution de commandes avec des astuces (ou en utilisant la $(...)notation si vous préférez), la liste complète des noms produits par findest collée sur la ligne de commande en tant qu'arguments tar, ce qui l'amène à les écrire tous en même temps.

Nicole Hamilton
la source
2
Cela pourrait finir mauvais si trouver des fichiers de sortie avec des espaces dans leur nom, des nouvelles lignes ou des caractères globbing. Cela est voué à l'échec - la sortie standard de la tuyauterie findest rarement une bonne idée. mywiki.wooledge.org/ParsingLs
slhck
3
@slhck, canaliser la sortie standard de find est en fait généralement une bonne idée, comme cela est très clairement expliqué dans la page à laquelle vous avez lié dans votre commentaire :). C'est en fait la façon recommandée de faire les choses. Vous devriez simplement utiliser quelques astuces (comme celle read -rde -print0) comme je l'ai fait dans ma réponse.
terdon
4
@slhck C'est pourquoi les noms de fichiers et de répertoires sous Unix et Linux ont traditionnellement évité les espaces dans les noms. C'est aussi pourquoi, sous Windows, où les noms avec des espaces sont courants, j'ai ajouté une notation de substitution de commande supplémentaire à mon propre shell Hamilton C à l' aide de doubles backticks qui traitent les lignes entières (y compris éventuellement les espaces) comme des mots uniques à coller sur la commande ligne. Malheureusement, aucun des shells Unix n'a cette fonctionnalité.
Nicole Hamilton
1
Ils l'ont peut-être traditionnellement évité, mais avec les fichiers créés dans l'espace utilisateur via des interfaces graphiques, vous ne pouvez plus négliger les fichiers avec des espaces et les traiter comme des citoyens de seconde classe (simplement parce que c'est Unix). C'est bien que vous l'ayez inclus dans votre shell, mais c'est pour Windows, et les shells Unix n'ont pas particulièrement besoin de cette fonctionnalité si vous utilisez simplement la bonne syntaxe et prenez les précautions appropriées. C'est pourquoi j'ai publié mon commentaire en premier lieu.
slhck
2
Non, mais dans d'autres endroits, cela pourrait très bien se produire. C'est pourquoi c'est une bonne idée de programmer de manière défensive - mieux vaut prévenir que guérir. De plus, les visiteurs qui trouvent cette question peuvent ne pas nécessairement avoir exactement le même problème et se demandent pourquoi la commande qu'ils ont trouvée ici semble fonctionner pour ce cas précis, mais a échoué pour eux. Je vous laisse le soin de corriger la commande, je pensais juste qu'il était important de le mentionner car de nombreuses personnes rencontrent ce problème tôt ou tard.
slhck
6

question 1

Votre commande échoue car tarprend chacun des fichiers trouvés et les archive file.tar. Chaque fois qu'il le fait, il écrase le fichier créé précédemment file.tar.

Si vous voulez une seule archive avec tous les fichiers, exécutez simplement tardirectement, il n'y a pas besoin de find(et oui, cela fonctionne pour les fichiers avec des espaces dans leurs noms):

tar -vcf file.tar /var/log/anaconda*   

question 2

Les deux commandes sont complètement différentes:

  • find var / log recherchera un répertoire appelé var/log qui est un sous-répertoire de votre répertoire actuel , il est équivalent à find ./var/log(remarquez le ./).

  • trouver / var / log recherchera un répertoire appelé /var/log qui est un sous - répertoire de la racine,/ .

Le /message principal vient de tar, non find. Cela signifie qu'il supprime le premier /de vos noms de fichiers pour transformer les chemins absolus en relatifs . Cela signifie que le fichier de /var/log/anaconda.errorsera extrait ./var/log/anaconda.errorlorsque vous décompacterez l'archive.

terdon
la source
1

Il y a deux façons de -exectravailler. Une façon exécute la commande plusieurs fois - une fois pour chaque fichier; dans l'autre sens, la commande est exécutée une fois, y compris tous les fichiers sous forme de liste de paramètres.

  • -exec tar -cvf file.tar {} ';'exécute la tarcommande pour chaque fichier, en écrasant l'archive à chaque fois.
  • -exec tar -cvf file.tar {} '+'exécute la tarcommande une fois, créant une archive de tous les fichiers trouvés.
mwfearnley
la source
1

Je pense que l'utilisation de -exec pour chaque fichier peut ralentir la compression tar, si vous avez beaucoup de fichiers. Je préfère utiliser la commande:

find . -iname "*.jpg" | cpio -ov -H tar -F jpgs.tar
fabceolin
la source
jusqu'à ce qu'il commence à échouer avec/bin/cpio: xxx: Cannot open: Too many open files
SYN