Ich habe bestimmte freigegebene Netzwerkordner im /media
Verzeichnis gemountet.
Ich möchte sicherstellen, dass sudo find / -name foo
das /media
Verzeichnis immer übersprungen wird, wenn ich so etwas mache.
Ich möchte dem find
Befehl keinen Parameter übergeben ... Ich möchte mein System so konfigurieren, dass Verzeichnisse find
standardmäß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 -inum
kann der Parameter Abhilfe schaffen. Inode-Nummern sind nur pro gemountetem Dateisystem eindeutig. Um sie auszuschließen, /media
müssen wir also das Tupel identifizieren, das aus dem Dateisystem und der Inode-Nummer besteht.
Das folgende (lange) Skript schließt die Fälle /media
fü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, $HOME
wenn 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 find
sehr wählerisch hinsichtlich der Reihenfolge seiner Argumente ist. Das path
muss das erste Argument sein. Daher speichern wir das path
in einer lokalen Variable und holen es aus der Argumentliste ( shift
). Dann führen wir das ursprüngliche find command find
mit dem Pfad und allen verbleibenden Argumenten aus $@
. Das command
vor dem find
stellt sicher, dass wir nicht mit einem rekursiven Aufruf enden.
Mit der neuen ~/.bashrc
Datei 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, find
das einige Argumente einfügt, um find
die 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 $PATH
ausfü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/findcmd
find
find
Das installierte Skript wird danach für immer darauf angewiesen sein, dass die umbenannte findcmd
ausfü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 $0cmd
called 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 find
haben .find
findcmd
../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 -H
oder -L
wirksam war und -P
sie 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 -H
diese -L
wirksam sind). Damit ich mich bei dieser Sache wohler fühle, habe ich es so eingerichtet, dass /proc
und /sys
und standardmäßig /dev
bei 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. $0cmd
Obwohl es in diesen Fällen die Pfadsuche nicht blockieren kann, beeinflusst es auch find
sonst 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 „ eval
at 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, exec
lautet 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 while
Schleife 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 $i
gedruckt :$i
printf ' "${'$i}\"
...
"${1}"
Es durchläuft eine Schleife über Aufrufe von chk()
, erhält pro Iteration eine Kopie seiner Argumente und shift
verschiebt 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
$M
gesetzt 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
%b
Escape mit der Länge Null oder einer fehlgeschlagenen Konvertierung%.d
desselben und mit der gleichen Länge in eine Ezimalzahl, wenn$1
nicht festgelegt und das Ende der Argumente erreicht wurde. Dieser Fehler beendet diewhile
Schleife.
- Schreibt den Wert von
chk(){ ${1+:} exit; }
- Wenn
printf
dies erfolgreich ist,chk()
hat dies den einzigen Versuch unternommen, überhaupt Argumente zu ändern. Von diesem Punkt anwhile
kann die Schleife die restlichen Argumente weiter verarbeiten und drucken, aber siechk()
wird überhaupt nichts tun, bis alle Argumente erschöpft sind. An diesem Punkt wird nurexit
die Subshell ausgeführt. Und wenn das zweite Muster auch nur einmal übereinstimmt, stimmt keines der anderen mehr überein.
- Wenn
(-?*)
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$O
gesetzt 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 abgetrenntgetopts
und 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 definierenchk()
. Auf diese Weise wird jede Argumentsequenz, die nicht explizit behandelt wird, einfach wörtlich durchgereicht undfindcmd
macht mit den Ergebnissen, was sie will.Für jede übereinstimmende Anfangsoption wird
-[HL]
die Flag-Variable$L
auf 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 undM=
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$L
nicht gesetzt, ist der! ! -L "${L-$2}"
Test erfolgreich, wenn$2
es 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$rt
auf 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.