La tuyauterie pour la sortie de boucle empêche la modification de variable locale

11

J'essaie d'écrire une fonction bash simple qui prend, comme arguments, un certain nombre de fichiers et / ou de répertoires. Cela devrait:

  1. Qualifiez entièrement les noms de fichiers.
  2. Triez-les.
  3. Supprimez les doublons.
  4. Imprimez tout ce qui existe réellement.
  5. Renvoie le nombre de fichiers inexistants.

J'ai un script qui fait presque ce que je veux, mais tombe sur le tri. La valeur de retour du script tel qu'il est est correcte, mais la sortie ne l'est pas (non triée et en double). Si je décommente l' | sort -uinstruction comme indiqué, la sortie est correcte mais la valeur de retour est toujours 0.

NB Des solutions plus simples pour résoudre le problème sont les bienvenues mais la question est vraiment de savoir pourquoi cela se produit dans le code que j'ai. C'est-à-dire, pourquoi l'ajout du tuyau semble-t-il arrêter le script d'incrémenter la variable r?

Voici le script:

function uniqfile
{
    local r=0 

    for arg in "$@"
    do  
        readlink -e "$arg" || (( ++r ))

    done #| sort -u    ## remove that comment

    return $r
}
tjm
la source
Juste une petite observation. Vous pouvez réduire for arg in "$@"à for arg. "Si 'en MOTS ...;' n'est pas présent, alors 'in "$ @"' est supposé. " - aide pour
manatwork

Réponses:

15

Il s'agit d'un piège bash bien connu, en raison de cette fonctionnalité :

Chaque commande d'un pipeline est exécutée comme un processus distinct (c'est-à-dire dans un sous-shell).

afin que les variables modifiées soient locales au sous-shell, et non visibles une fois de retour dans le parent.

Pour éviter cela, reformulez votre code pour éviter le pipeline, avec une substitution de processus:

 for arg in "$@"
    do  
        readlink -e "$arg" || (( ++r ))

    done > >(sort -u)
enzotib
la source
Merci. C'est génial. Je me demande si vous pourriez me dire le nom de la >(..command..)construction. Je pense que je sais comment cela fonctionne, mais je pense que je devrais faire quelques lectures supplémentaires.
tjm
2
@tjm: cela s'appelle la substitution de processus
enzotib
La substitution de processus dans Bash a plusieurs formes: tldp.org/LDP/abs/html/process-sub.html
slm
La substitution de processus est une forme de communication inter-processus qui permet à l'entrée ou à la sortie d'une commande d'apparaître sous forme de fichier. La commande est remplacée en ligne, là où un nom de fichier se produirait normalement , par le shell de commande. Cela permet aux programmes qui n'acceptent normalement que des fichiers de lire directement ou d'écrire dans un autre programme.
nobar
3

Le | sort -uforce le bit précédent (donc la boucle for entière) à s'exécuter dans un sous-processus (bash a besoin d'un 'STDOUT' pour rediriger vers le sort'STDIN'. (Internet semble penser kshet bashgérer ce cas légèrement différemment .. premier ou dernier commande dans la séquence de tuyaux est placée dans un sous-shell?)

Ce fil passe en revue un problème similaire et a une solution soignée à la fin: http://ubuntuforums.org/showthread.php?t=312017

extrait
    #!/bin/bash
    exec 3< <(du | sort -n)  

    n=0
    while read size dir; do
      [ $size -gt 1000 ] && ((n++))
    done <&3
    exec 3<&-

    echo "Found $n too big files"
PT
la source