Das Piping für die Schleifenausgabe verhindert die Änderung lokaler Variablen

11

Ich versuche, eine einfache Bash-Funktion zu schreiben, die als Argumente eine Reihe von Dateien und / oder Verzeichnissen verwendet. Es sollte:

  1. Qualifizieren Sie die Dateinamen vollständig.
  2. Sortieren Sie sie.
  3. Duplikate entfernen.
  4. Drucken Sie alles aus, was tatsächlich vorhanden ist.
  5. Gibt die Anzahl der nicht vorhandenen Dateien zurück.

Ich habe ein Skript, das fast macht, was ich will, aber auf die Sortierung fällt. Der Rückgabewert des Skripts in seiner jetzigen Form ist korrekt, die Ausgabe jedoch nicht (unsortiert und dupliziert). Wenn ich die | sort -uAnweisung wie angegeben auskommentiere , ist die Ausgabe korrekt, der Rückgabewert jedoch immer 0.

NB Einfachere Lösungen zur Lösung des Problems sind willkommen, aber die Frage ist wirklich, warum dies in dem Code auftritt, den ich habe. Das heißt, warum verhindert das Hinzufügen der Pipe scheinbar das Inkrementieren der Variablen durch das Skript r?

Hier ist das Skript:

function uniqfile
{
    local r=0 

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

    done #| sort -u    ## remove that comment

    return $r
}
tjm
quelle
Nur eine kleine Beobachtung. Sie können reduzieren for arg in "$@"zu for arg. "Wenn 'in WORTEN ...;' ist nicht vorhanden, dann wird 'in "$ @"' angenommen. " - Hilfe für
Manatwork

Antworten:

15

Dies ist aufgrund dieser Funktion eine bekannte Bash-Falle :

Jeder Befehl in einer Pipeline wird als separater Prozess ausgeführt (dh in einer Unterschale).

Damit sind geänderte Variablen lokal für die Subshell und nicht mehr im übergeordneten Element sichtbar.

Um dies zu vermeiden, formulieren Sie Ihren Code neu, um die Pipeline mit einer Prozessersetzung zu vermeiden:

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

    done > >(sort -u)
Enzotib
quelle
Vielen Dank. Das ist großartig. Ich frage mich, ob Sie mir den Namen des >(..command..)Konstrukts sagen könnten . Ich denke, ich weiß, wie es funktioniert, aber ich denke , ich sollte etwas weiter lesen.
tjm
2
@tjm: es heißt Prozesssubstitution
Enzotib
Die Prozessersetzung in Bash hat viele Formen: tldp.org/LDP/abs/html/process-sub.html
slm
Die Prozessersetzung ist eine Form der Kommunikation zwischen Prozessen, mit der die Eingabe oder Ausgabe eines Befehls als Datei angezeigt werden kann. Der Befehl wird in der Zeile, in der normalerweise ein Dateiname vorkommt , durch die Befehlsshell ersetzt. Auf diese Weise können Programme, die normalerweise nur Dateien akzeptieren, direkt aus einem anderen Programm lesen oder in dieses schreiben.
Nobar
3

Das | sort -uerzwingt, dass das vorhergehende Bit (also die gesamte for-Schleife) in einem Unterprozess ausgeführt wird (bash benötigt ein 'STDOUT', um in das sort'STDIN' umzuleiten. (Das Internet scheint diesen Fall etwas anders zu denken kshund zu bashbehandeln. Zuerst oder zuletzt Befehl in der Pipe-Sequenz wird in eine Subshell gesetzt?)

Dieser Thread behandelt ein ähnliches Problem und hat am Ende eine gute Lösung: http://ubuntuforums.org/showthread.php?t=312017

Auszug
    #!/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
quelle