xargs avec redirection stdin / stdout

21

Je voudrais courir:

./a.out < x.dat > x.ans

pour chaque * .dat fichier dans le répertoire A .

Bien sûr, cela pourrait être fait par bash / python / quel que soit le script, mais j'aime écrire un one-liner sexy. Tout ce que je pouvais atteindre est (toujours sans stdout):

ls A/*.dat | xargs -I file -a file ./a.out

Mais -a dans xargs ne comprend pas le 'fichier' replace-str.

Merci pour l'aide.

Nikolay Vyahhi
la source
En plus des autres bonnes réponses ici, vous pouvez utiliser l'option -a de GNU xargs.
James Youngman

Réponses:

30

Tout d'abord, n'utilisez pas la lssortie comme liste de fichiers . Utilisez l'extension shell ou find. Voir ci-dessous pour les conséquences potentielles d'une mauvaise utilisation de ls + xargs et un exemple d' xargsutilisation appropriée .

1. Manière simple: pour la boucle

Si vous souhaitez traiter uniquement les fichiers ci-dessous A/, une simple forboucle devrait suffire:

for file in A/*.dat; do ./a.out < "$file" > "${file%.dat}.ans"; done

2. pre1 Pourquoi pas   ls | xargs ?

Voici un exemple de la façon dont les choses peuvent mal tourner si vous utilisez lsavec xargspour le travail. Considérez un scénario suivant:

  • commençons par créer des fichiers vides:

    $ touch A/mypreciousfile.dat\ with\ junk\ at\ the\ end.dat
    $ touch A/mypreciousfile.dat
    $ touch A/mypreciousfile.dat.ans
    
  • voir les fichiers et qu'ils ne contiennent rien:

    $ ls -1 A/
    mypreciousfile.dat
    mypreciousfile.dat with junk at the end.dat
    mypreciousfile.dat.ans
    
    $ cat A/*
    
  • exécutez une commande magique en utilisant xargs:

    $ ls A/*.dat | xargs -I file sh -c "echo TRICKED > file.ans"
    
  • le résultat:

    $ cat A/mypreciousfile.dat
    TRICKED with junk at the end.dat.ans
    
    $ cat A/mypreciousfile.dat.ans
    TRICKED
    

Vous venez donc de remplacer les deux mypreciousfile.datet mypreciousfile.dat.ans. S'il y avait du contenu dans ces fichiers, il aurait été effacé.


2. Utilisation   xargs : la bonne façon avec  find 

Si vous souhaitez insister sur l'utilisation xargs, utilisez -0(noms à terminaison nulle):

find A/ -name "*.dat" -type f -print0 | xargs -0 -I file sh -c './a.out < "file" > "file.ans"'

Remarquez deux choses:

  1. de cette façon, vous créerez des fichiers avec une .dat.ansfin;
  2. cela se cassera si un nom de fichier contient un signe de citation ( ").

Les deux problèmes peuvent être résolus par différentes méthodes d'appel du shell:

find A/ -name "*.dat" -type f -print0 | xargs -0 -L 1 bash -c './a.out < "$0" > "${0%dat}ans"'

3. Tout est fait en dedans find ... -exec

 find A/ -name "*.dat" -type f -exec sh -c './a.out < "{}" > "{}.ans"' \;

Encore une fois, cela produit des .dat.ansfichiers et se cassera si les noms de fichiers contiennent ". Pour ce faire, utilisez bashet modifiez la façon dont elle est invoquée:

 find A/ -name "*.dat" -type f -exec bash -c './a.out < "$0" > "${0%dat}ans"' {} \;
rozcietrzewiacz
la source
+1 pour avoir mentionné de ne pas analyser la sortie de ls.
rahmu
2
L'option 2 se casse lorsque les noms de fichiers contiennent ".
thiton
2
Très bon point, merci! Je mettrai à jour en conséquence.
rozcietrzewiacz
Je veux juste mentionner que si zshest utilisé comme shell (et SH_WORD_SPLIT n'est pas défini), tous les cas spéciaux désagréables (espaces blancs, "dans le nom de fichier, etc.) ne doivent pas être pris en compte. Le trivial for file in A/*.dat; do ./a.out < $file > ${file%.dat}.ans ; donefonctionne dans tous les cas.
jofel
-1 parce que j'essaie de comprendre comment faire des xargs avec stdin et ma question n'a rien à voir avec les fichiers ou find.
Mehrdad
2

Essayez de faire quelque chose comme ça (la syntaxe peut varier un peu selon le shell que vous utilisez):

$ for i in $(find A/ -name \*.dat); do ./a.out < ${i} > ${i%.dat}.ans; done

rahmu
la source
Ça ne marcherait pas. Il essaierait de fonctionner sur des trucs commesomefile.dat.dat et de rediriger toutes les sorties vers un seul fichier.
rozcietrzewiacz
Tu as raison. J'ai édité la solution pour la corriger.
rahmu
OK - Presque bien :) Juste somefile.dat.ansdes trucs de sortie n'auraient pas l'air si bien.
rozcietrzewiacz
1
Édité! Je ne connaissais pas «%». Cela fonctionne comme un charme, merci pour le conseil.
rahmu
1
Ajouter un -type fileserait bien (impossible < directory), et cela rend triste la fée du nom de fichier inhabituel.
Mat
2

Pour les modèles simples, la boucle for est appropriée:

for file in A/*.dat; do
    ./a.out < "${file}" > "${file%.dat}.ans" # Never forget the QUOTES!
done

Pour les cas plus complexes où vous avez besoin d'un autre utilitaire pour répertorier les fichiers (zsh ou bash 4 ont des modèles assez puissants que vous avez rarement besoin de trouver, mais si vous voulez rester dans le shell POSIX ou utiliser un shell rapide comme un tiret, vous aurez besoin de trouver pour tout non triviale), tandis que la lecture est la plus appropriée:

find A -name '*.dat' -print | while IFS= read -r file; do
   ./a.out < "${file}" > "${file%.dat}.ans" # Never forget the QUOTES!
done

Cela gérera les espaces, car la lecture est (par défaut) orientée ligne. Il ne gérera pas les sauts de ligne et ne gérera pas les barres obliques inverses, car par défaut, il interprète les séquences d'échappement (qui vous permet en fait de passer un saut de ligne, mais find ne peut pas générer ce format). De nombreux shells ont l' -0option de lire, donc dans ceux-ci, vous pouvez gérer tous les caractères, mais malheureusement ce n'est pas POSIX.

Jan Hudec
la source
2

Utilisez GNU Parallel:

parallel ./a.out "<{} >{.}.ans" ::: A/*.dat

Bonus supplémentaire: vous obtenez le traitement en parallèle.

Regardez les vidéos d'introduction pour en savoir plus: http://www.youtube.com/watch?v=OpaiGYxkSuQ

Ole Tange
la source
1

Je pense que vous avez besoin d'au moins une invocation de shell dans les xargs:

ls A/*.dat | xargs -I file sh -c "./a.out < file > file.ans"

Edit: Il convient de noter que cette approche ne fonctionne pas lorsque les noms de fichiers contiennent des espaces. Je ne peux pas travailler. Même si vous avez utilisé find -0 et xargs -0 pour que les xargs comprennent correctement les espaces, l'appel de shell -c les croasserait. Cependant, l'OP a explicitement demandé une solution xargs, et c'est la meilleure solution xargs que j'ai trouvée. Si les espaces dans les noms de fichiers peuvent poser problème, utilisez find -exec ou une boucle shell.

thiton
la source
@rozcietrzewiacz: En général, bien sûr, mais je suppose que quelqu'un qui essaie de faire du xargs vaudou le sait. Étant donné que ces noms de fichiers subissent une autre expansion de la coque, ils doivent de toute façon se comporter correctement.
2011
1
Vous vous trompez sur l'expansion du shell. lsgénère des choses comme des espaces sans s'échapper et c'est le problème.
rozcietrzewiacz
@rozcietrzewiacz: Oui, je comprends ce problème. Supposons maintenant que vous échappiez correctement ces espaces et les introduisiez dans des xargs: ils sont remplacés dans la chaîne -c de sh, sh symbolise la chaîne -c, et tout se casse.
2011
Oui. Vous voyez maintenant, l'analyse lsn'est pas bonne. Mais xargs peut être utilisé en toute sécurité avec find - voir la suggestion n ° 2 dans ma réponse.
rozcietrzewiacz
1

Inutile de compliquer les choses. Vous pouvez le faire avec une forboucle:

for file in A/*.dat; do
  ./a.out < "$file" >"${file%.dat}.ans"
done

le ${file%.dat}.ansbit supprimera le .datsuffixe du nom de fichier du nom de fichier $fileet l'ajoutera .ansà la fin.

Kusalananda
la source