Linux Find-Befehl überspringt ein Verzeichnis

Linux Find-Befehl überspringt ein Verzeichnis

Ich habe bestimmte freigegebene Netzwerkordner im /mediaVerzeichnis gemountet.

Ich möchte sicherstellen, dass sudo find / -name foodas /mediaVerzeichnis immer übersprungen wird, wenn ich so etwas mache.

Ich möchte dem findBefehl keinen Parameter übergeben ... Ich möchte mein System so konfigurieren, dass Verzeichnisse findstandardmäßig immer übersprungen werden /media.

Antwort1

In dieser Situation müssen mehrere Randfälle berücksichtigt werden. Der erste Ansatz find / -path '/media' -prune -o ...ist nur dann ausreichend, wenn der Suchpfad absolut ist und mit beginnt /. Das Szenario cd / && find * ...wird nie mit der -path '/media'Klausel übereinstimmen.

Glücklicherweise -inumkann der Parameter Abhilfe schaffen. Inode-Nummern sind nur pro gemountetem Dateisystem eindeutig. Um sie auszuschließen, /mediamüssen wir also das Tupel identifizieren, das aus dem Dateisystem und der Inode-Nummer besteht.

Das folgende (lange) Skript schließt die Fälle /mediafür Sie aus und erkennt hoffentlich genügend Randfälle, um von Nutzen zu sein.


#!/bin/bash
#
FIND=/usr/bin/find


# Process prefix arguments
#
opt_H= opt_L= opt_P= opt_D= opt_O=
while getopts 'HLPD:O:' opt
do
    case "$opt" in
        H)      opt_H=-H ;;
        L)      opt_L=-L ;;
        P)      opt_P=-P ;;
        D)      opt_D="-D $OPTARG" ;;
        O)      opt_O="-O $OPTARG" ;;
    esac
done
shift $((OPTIND - 1))


# Find the inode number for /media and its filesystem
#
m_inode=$(stat -c '%i' /media 2>/dev/null)
m_fsys=$(stat -c '%m' /media 2>/dev/null)


# Collect the one or more filesystem roots to search
#
roots=()
while [[ 0 -lt $# && "$1" != -* ]]
do
    roots+=("$1")
    shift
done


# Collect the "find" qualifiers. Some of them need to be at the front
# of the list. Unfortunately.
#
pre_args=() args=()
while [[ 0 -lt $# ]]
do
    # We really ought to list all qualifiers here, but I got tired of
    # typing for an example
    #
    case "$1" in
        -maxdepth)      pre_args+=("$1"); pre_args+=("$2"); shift 2 ;;
        -mindepth)      pre_args+=("$1"); pre_args+=("$2"); shift 2 ;;
        -mount|-xdev)   pre_args+=("$1"); shift ;;
        -depth|-d)      pre_args+=("$1"); shift ;;
        -name|-iname)   args+=("$1"); args+=("$2"); shift 2 ;;
        -path|-ipath)   args+=("$1"); args+=("$2"); shift 2 ;;
        *)              args+=("$1") ; shift ;;
    esac
done
test -z "${args[*]}" && args=('-print')


# Iterate across the collected filesystem roots, attempting to skip
# /media only if the filesystem matches
#
exit_ss=0
for root in "${roots[@]}"
do
    fsys=$(stat -c '%m' "$root" 2>/dev/null)
    if [[ -n "$m_inode" && -n "$m_fsys" && "$fsys" == "$m_fsys" ]]
    then
        # Same filesystem. Exclude /media by inode
        #
        "$FIND" ${opt_H:+"$opt_H"} ${opt_L:+"$opt_L"} \
                ${opt_P:+"$opt_P"} ${opt_O:+"$opt_O"} \
                ${opt_O:+"$opt_O"} "$root" "${pre_args[@]}" \
                \( -inum "$m_inode" -prune \) -o \( "${args[@]}" \)
        ss=$?
        [[ 0 -lt $ss ]] && exit_ss="$ss"
    else
        # Different filesystem so we don't need to worry about /media
        #
        "$FIND" ${opt_H:+"$opt_H"} ${opt_L:+"$opt_L"} \
                ${opt_P:+"$opt_P"} ${opt_O:+"$opt_O"} \
                ${opt_O:+"$opt_O"} "$root" "${pre_args[@]}" \
                "${pre_args[@]}" \( "${args[@]}" \)
        ss=$?
        [[ 0 -lt $ss ]] && exit_ss="$ss"
    fi
done


# All done
#
exit $exit_ss

Antwort2

Wenn Sie bei der „einfachen“ Verwendung von bleiben möchten find(also nicht mehrere Verzeichnisse, keine Optionen -H -L -D -P -O) und mit der Verwendung der Option einverstanden sind -xdev, versuchen Sie diese einfache Antwort. Dadurch werden alle gemounteten Dateisysteme ausgeschlossen (z. B. auch, $HOMEwenn sie separat gemountet sind).

Sie können eine Bash-Funktion so definieren find, dass sie nicht in andere Dateisysteme gelangt. Fügen Sie dies in Ihr ~/.bashrc(vorausgesetzt Sie verwenden bash) ein.

find () {
  local path="${1}"
  shift
  command find "${path}" -xdev "${@}"
}

Erklärung: Wir müssen eine Funktion anstelle eines Alias ​​verwenden, da findsehr wählerisch hinsichtlich der Reihenfolge seiner Argumente ist. Das pathmuss das erste Argument sein. Daher speichern wir das pathin einer lokalen Variable und holen es aus der Argumentliste ( shift). Dann führen wir das ursprüngliche find command findmit dem Pfad und allen verbleibenden Argumenten aus $@. Das commandvor dem findstellt sicher, dass wir nicht mit einem rekursiven Aufruf enden.

Mit der neuen ~/.bashrcDatei müssen Sie diese zuerst als Quelle verwenden.

source ~/.bashrc

Dann können Sie die neue Version von verwenden find. Sie können die Definition jederzeit überprüfen mit

> type find
find is a function
find ()
{
    local path="${1}";
    shift;
    command find "${path}" -xdev "${@}"
}

Antwort3

(   set -e -- "$(command -v find)"
    [ -x "${1:?}"  ]
    [ ! -e "$1cmd" ]
    [ ! -L "$1cmd" ]
    mv -- "$1"  "$1cmd"
    cat > "$1"
    chmod +x -- "$1"
)   <<""
#!/bin/sh -f
eval '  exec    "$0cmd" '"${1$(                       # f!'"ing colors
        unset   i L O M rt IFS
        chk()   case   ${O+$2}${2--}    in            # $O must be set or $2
                (-maxdepth"$2") M=      ;;            # unset to match "$2$2"
                ([\(!]"$2"|-*"$2")                    # this is the last match
                        printf  %s${1+%b}%.d\
                               "$rt" \\c 2>&-   &&    # printf fails if ! $1  
                        chk(){  ${1+:} exit; }  ;;    # chk() = !!$1 || exit
                (-?*)   shift   $((OPTIND=1))         # handle -[HLP] for 
                        while   getopts :HLP    O     # path resolution w/
                        do      case    $O${L=} in    # NU$L expansions
                                (P)     unset L ;;    # $[HL]=:- $P=-
                                (\?)    rt= chk ''    # opt unexpected  &&
                                        return  ;;    # abandon parse
                        esac;   done;   unset O ;;    # $O is unset until 
                (${M-${O=?*}})                        # above matches fail
                      ! [ ! -L "${L-$2}" ]      ||    # ! -P ||!! -L ||
                        [ !  / -ef "$2"  ]      ||    # !  / == $2   ||
                        rt=$rt' ! \( -path "${'$i'%/}/media/*" -prune \)'
                esac
        while   chk ${1+$((i+=1)) "$1"}               # loop while args remain
        do      printf ' "${'$i}\"                    # printf args to eval
                shift                                 # shift args away
        done                                          # done
)"

Dafür gibt es ein Wrapper-Skript, finddas einige Argumente einfügt, um finddie Suche des Real-Rechners zu verhindern /media/, wenn eines seiner Pfadargumente gleich ist /.

Das obige Skript besteht aus zwei Teilen: dem eigentlichen Skript(das ist alles, was folgt<<""\n) und oben befindet sich der einmalige Installationsteil(das ist alles zwischen dem ersten übereinstimmenden (Klammernpaar )).

Installieren

(   set -e -- "$(command -v find)"         #get /path/to/find
    [ -x "${1:?}"  ]                       #else loudly fail
    [ ! -e "$1cmd" ]                       #fail if /path/to/findcmd
    [ ! -L "$1cmd" ]                       #and double-check
    mv -- "$1"  "$1cmd"                    #rename .../find -> .../findcmd
    cat > "$1"                             #copy stdin to .../find
    chmod +x -- "$1"                       #set new find's executable bit
)   <<"" ###stdin

Die Installation erfordert etwas Sorgfaltnichterfolgreich abgeschlossen werden, es sei denn, es besteht eine vernünftige Chance, dass dies gelingt, ohne etwas auf Ihrem System direkt zu ändern, außer dem Dateinamen Ihrer $PATHausführbaren Datei. Diesen will es von in findändern und wird den Versuch unternehmen, wenn er noch nicht existiert. Wenn seine Tests zutreffen – und Sie überhaupt über die entsprechenden Berechtigungen zum Anwenden der Befehle verfügen – wird es die ausführbare Datei umbenennen und an ihrer Stelle ein neues Shell-Skript mit dem Namen installieren ./path/to/find/path/to/findcmd/path/to/findcmdfindfind

Das installierte Skript wird danach für immer darauf angewiesen sein, dass die umbenannte findcmdausführbare Datei dort bleibt, wo sie hinterlassen wurde.(Sie sollten Ihren Paketmanager hierüber informieren, falls Sie einen verwenden)und bei jedem Aufruf ersetzt es sich selbst durch $0cmdcalled mit all seinen Argumenten, nachdem es einen Blick auf sie geworfen hat. Wenn Sie nicht alles Notwendige tun, um die Installation dauerhaft zu machen, überschreiben die meisten Paketmanager das installierte Skript irgendwann mit einer neu aktualisierten Binärdatei, und Sie sind wieder genau dort, wo Sie angefangen haben, außer dass Sie auch eine ältere namens im Systemverzeichnis findhaben .findfindcmd../bin

Sofern die entsprechenden Berechtigungen vorliegen und Ihr System keine unliebsamen Überraschungen bereithält, sollte das gesamte Skript durch Kopieren und Einfügen in eine Shell-Eingabeaufforderung selbstinstallierbar sein.(Sie müssen am Ende jedoch zusätzlich RETURN eingeben)Wenn es auf diese Weise nicht funktioniert, dann dürfte der Versuch zumindest keinen Schaden angerichtet haben.

Neufund

#!/bin/sh -f
eval '  exec    "$0cmd" '"${1+$(                      # f!'"ing colors
        unset   i L O M rt IFS
        chk()   case   ${O+$2}${2--}    in            # $O must be set or $2
                (-maxdepth"$2") M=      ;;            # unset to match "$2$2"
                ([\(!]"$2"|-*"$2")                    # this is the last match
                        printf  %s${1+%b}%.d\
                               "$rt" \\c 2>&-   &&    # printf fails if ! $1  
                        chk(){  ${1+:} exit; }  ;;    # chk() = !!$1 || exit
                (-?*)   shift   $((OPTIND=1))         # handle -[HLP] for 
                        while   getopts :HLP    O     # path resolution w/
                        do      case    $O${L=} in    # NU$L expansions
                                (P)     unset L ;;    # $[HL]=:- $P=-
                                (\?)    rt= chk ''    # opt unexpected  &&
                                        return  ;;    # abandon parse
                        esac;   done;   unset O ;;    # $O is unset until 
                (${M-${O=?*}})                        # above matches fail
                      ! [ ! -L "${L-$2}" ]      ||    # ! -P ||!! -L ||
                        [ !  / -ef "$2"  ]      ||    # !  / == $2   ||
                        rt=$rt' ! \( -path "${'$i'%/}/media/*" -prune \)'
                esac
        while   chk ${1+$((i+=1)) "$1"}               # loop while args remain
        do      printf ' "${'$i}\"                    # printf args to eval
                shift                                 # shift args away
        done                                          # done
)}"

Meine erste und wichtigste Regel beim Schreiben eines Wrapper-Skripts lautet:Hände weg. Wenn ich ein Programm bräuchte, würde ich versuchen, eines zu schreiben, aber da ich bereits ein Programm habe, das es wert ist, umschlossen zu werden, werde ich versuchen, es ungehindert das tun zu lassen, was es bereits tut, und sein Verhalten so wenig wie möglich zu ändern, um mein Endziel zu erreichen. Das bedeutet, dass ich nichts tun sollte, was seine Ausführungsumgebung in irgendeiner Weise beeinflussen könnte, die nicht direkt mit dem Zweck der Umschließung zusammenhängt. Ich lege also keine Variablen fest, interpretiere keine Argumente, berühre keine E/A-Streams und ändere weder die Prozessgruppe des umschlossenen Programms noch seine übergeordnete PID. In allen Dingen sollte die Umschließung so flüchtig und transparent wie möglich sein.

Das obige Skript erreicht dieses Ziel, jetzt mehr als zuvor. Ich war vorher nicht zufrieden – insbesondere in Bezug auf Pfadauflösungen – aber ich glaube, ich habe das behoben. Um dies richtig zu machen, musste ich den [HLP]Status verfolgen, damit ich symbolische Links korrekt vergleichen konnte, /wenn eine der Optionen -Hoder -Lwirksam war und -Psie nicht negierte. Wenn der Linktest erfolgreich ist, wird das aktuelle Argument auf eine -efÜbereinstimmung mit dem gleichen Datei-Inode geprüft /– was bedeutet, dass praktisch jeder Name für /funktioniert(um symbolische Links einzuschließen, wenn -Hdiese -Lwirksam sind). Damit ich mich bei dieser Sache wohler fühle, habe ich es so eingerichtet, dass /procund /sysund standardmäßig /devbei Suchanfragen blockiert werden./

Besonders gut ist, dass es die Änderung seines aufgerufenen Status vermeidet, bevor es diesen an weitergibt $0cmd. Es lehnt es ausdrücklich ab, einen Argumentensatz zu interpretieren, der eine Option enthält, die es nicht verarbeiten kann, und gibt in diesen Fällen den gesamten Satz unverändert an weiter. $0cmdObwohl es in diesen Fällen die Pfadsuche nicht blockieren kann, beeinflusst es auch findsonst nicht das Verhalten von . Aus diesem Grund eval "exec wrapped_program $(arg-handler)"ist dies die Methode, die ich für diese Art von Dingen am meisten bevorzuge.

Höchststufe

Tatsächlich besteht das gesamte Shell-Skript, wie oben erwähnt, auf seiner obersten Ebene nur aus einem einzigen einfachen Befehl, der es anweist, sich selbst durch eine andere ausführbare Datei zu ersetzen. Jegliche Arbeit wird innerhalb der $(Befehlsersetzungs- )Subshell erledigt, und ihr gesamter Status – ob geändert oder nicht – ist vollständig lokalisiert. Der Zweck von „ evalat all“ besteht darin, einen zweiten Blick auf die Argumente des Skripts zu werfen, ohne sie tatsächlich unnötig beeinflussen zu müssen – und genau darum geht es bei diesem Wrapper.

Wenn der $(Befehl sub )seine Arbeit erledigt hat, execlautet der resultierende 'd-Befehl entweder:

exec "$0cmd" "${1}" ... ! \( -path "${[num]%/}/media/*" -prune \) "${2}" ...

...wobei alle ursprünglichen Argumente - sofern vorhanden - in ihrer ursprünglichen und unveränderten Form der Reihe nach und nach mit Nummern referenziert werden(sogar Nullargumente)zusätzlich zu den sechs( !, \(, -path, "${[num]%/}/media/*", -prune, \))Einfügungen, von denen eine Menge bei jedem erfolgreichen / -ef "${num}"Test während des Arg-Scans auftritt. Andernfalls lautet es einfach:

exec "$0cmd" "${1}" "${2}" "${3}" "${4}" ...

...wobei auf alle ursprünglichen Argumente auf die gleiche Weise verwiesen wird, ohne dass überhaupt Einfügungen vorgenommen werden.

Die einzigen zwei möglichen Änderungen, die dieser Wrapper an der Umgebung seines gewrappten Ziels vornehmen kann, sind diese:

  • Es ändert den Prozessnamen von seinem eigenen in seinen Namen +cmd. Das passiert immer.

  • Es kann sechs Argumente pro Stammübereinstimmung in die Liste derjenigen einfügen, mit denen es aufgerufen wurde.

Sofern die erste Änderung als akzeptabel gilt(obwohl es vermeidbar ist), es gibt hier einen einzigen Fehlerpunkt in Bezug auf die Verhaltensänderung – und zwar, ob die Argumenteinfügung gültig ist oder nicht. Alle Fehler, die sonst mit dem Aufruf verbunden sind, liegen einfach außerhalb des Gültigkeitsbereichs und sollten vom Wrap-Ziel behandelt werden, und dieser Wrapper versucht, sich um seine eigenen Angelegenheiten zu kümmern.

Arg-Handler

Bei der Befehlssubstitution initiere ich zuerst vars auf unset, da der ""String der beste Weg ist,nichtbei Bedarf einen Pfad abgleichen. Dann deklariere ich die chk()Funktion und rufe sie anschließend für jede Iteration der whileSchleife auf, die für jedes Aufrufargument des Skripts um eins erhöht wird . Jedes Inkrement wird in Anführungszeichen und Klammern eingeschlossen, denen ein Leerzeichen und ein Dollarzeichen vorangestellt sind, in die Standardausgabe des Befehlssubs $igedruckt :$i

printf ' "${'$i}\"

...

 "${1}"

Es durchläuft eine Schleife über Aufrufe von chk(), erhält pro Iteration eine Kopie seiner Argumente und shiftverschiebt sie dann, bis keines mehr übrig ist und die Schleife abgeschlossen ist. chk()vergleicht seine Argumente mit seinen Mustern und führt die entsprechenden Aktionen aus:

  • (-maxdepth"$2") M= ;;

    • Wenn $Mgesetzt ist, kann das letzte Muster nur mit einer Nullzeichenfolge übereinstimmen, die nur die nachfolgenden Pfadvergleichstests ihres Blocks nicht bestehen kann, und sort=$rt+!\(usw. tritt in diesem Fall nie auf. Andernfalls wird nichts unternommen.

    • Die POSIX-Spezifikation verlangt nur, -[HL]dass sie vor allen Operanden erkannt wird [...path...], alle anderen sind nicht spezifiziert. Hier ist, was sie darüber aussagt, welche [...path...]Operanden und welche Testoperanden sind:

      Der erste Operand und die nachfolgenden Operanden bis zum ersten Operanden, der mit a beginnt oder a ! oder a ist (, (ausschließlich) sind als [...path...]Operanden zu interpretieren. Wenn der erste Operand mit a beginnt oder a oder a ist , ist das Verhalten nicht angegeben. Jeder Pfadoperand ist ein Pfadname eines Startpunkts in der Dateihierarchie.!(

  • ([\(!]"$2"|-*"$2")

    • Das aktuelle Argument ist eine einzelne (linke Klammer oder ein !Ausrufezeichen, oder es beginnt mit einem -*Bindestrich, ist aber keiner -maxdepth, und das letzte Muster wurde mindestens einmal abgeglichen.

    • printf %s ${1+%b}%.d "$rt" \\c 2>&- &&

      • Schreibt den Wert von $rt– falls vorhanden – in die Standardausgabe der Befehlsersetzung, gefolgt von einem erfolgreichen Schreiben des \c %bEscape mit der Länge Null oder einer fehlgeschlagenen Konvertierung %.ddesselben und mit der gleichen Länge in eine Ezimalzahl, wenn $1nicht festgelegt und das Ende der Argumente erreicht wurde. Dieser Fehler beendet die whileSchleife.
    • chk(){ ${1+:} exit; }

      • Wenn printfdies erfolgreich ist, chk()hat dies den einzigen Versuch unternommen, überhaupt Argumente zu ändern. Von diesem Punkt an whilekann die Schleife die restlichen Argumente weiter verarbeiten und drucken, aber sie chk()wird überhaupt nichts tun, bis alle Argumente erschöpft sind. An diesem Punkt wird nur exitdie Subshell ausgeführt. Und wenn das zweite Muster auch nur einmal übereinstimmt, stimmt keines der anderen mehr überein.
  • (-?*)

    • Das aktuelle Argument ist mindestens zwei Zeichen lang und beginnt mit einem Bindestrich. Dieses Muster istmehrexklusiver als das -*"$2"Muster darüber, wenn es einmal $Ogesetzt ist, und so kann es immer nur übereinstimmen, bis mindestens ein einziges Argumentnichtpassen Sie es an. Auf diese Weise werden alle anfänglichen Optionen mit abgetrennt getoptsund mit abgeglichen [HPL]. Wenn eine anfängliche Option nicht zu diesem Muster passt, ruft sich die Funktion selbst rekursiv auf, um das darüber liegende Muster abzugleichen und neu zu definieren chk(). Auf diese Weise wird jede Argumentsequenz, die nicht explizit behandelt wird, einfach wörtlich durchgereicht und findcmdmacht mit den Ergebnissen, was sie will.

    • Für jede übereinstimmende Anfangsoption wird -[HL]die Flag-Variable $Lauf die Nullzeichenfolge gesetzt. Und für jede übereinstimmende Option gilt -P $L: unset.

  • (${M-${O=?*}})

    • Das erste auftretende Argument, das nicht übereinstimmt, -?*löst aus $O, dass auf das ?*Muster gesetzt wird. Danach kann jedes der ersten beiden Muster übereinstimmen ${O+$2}${2--}. Wenn jemals -maxdepth$2übereinstimmt und M=auf die Nullzeichenfolge gesetzt wird, kann dieses Muster nie wieder mit einem anderen Argument übereinstimmen, das nicht Null ist, und nur eine einzige Übereinstimmung mit dem zweiten Muster ist erforderlich, um alle Versuche, eines davon zuzuordnen, zu beenden.

    • Jedes Argument ungleich null, das nach der ersten Optionssequenz -[HLP]und vor einem anderen -*oder [\(?!]Argument auftritt, entspricht diesem Muster und wird auf Pfadauflösung getestet. Wenn $Lnicht gesetzt, ist der ! ! -L "${L-$2}"Test erfolgreich, wenn $2es sich um einen symbolischen Link oder einen ungültigen Pfadnamen handelt, schlägt aber andernfalls fehl, da kein Pfadname mit der ${L=}Nullzeichenfolge übereinstimmen kann.

    • Nur die Argumente, die den vorherigen Test nicht bestehen, werden auf eine !negierte Inode-Übereinstimmung überprüft, /und jedes Argument, das beide Tests nicht besteht, wird $rtauf sich selbst plus die Zeichenfolge gesetzt ! \( -path "${[num]%/}/media/* -prune \), die erst geschrieben wird, wenn das zweite Muster übereinstimmt oder das Ende der Argumente erreicht ist, je nachdem, was zuerst eintritt.

verwandte Informationen