У меня в каталоге смонтированы некоторые сетевые папки /media
.
Я хочу быть уверенным, что когда я делаю что-то подобное, каталог sudo find / -name foo
всегда будет пропущен /media
.
Я не хочу передавать параметр команде find
... Я хочу настроить свою систему таким образом, чтобы она find
всегда по умолчанию пропускала /media
каталог.
решение1
В этой ситуации необходимо рассмотреть ряд пограничных случаев. Первый подход find / -path '/media' -prune -o ...
достаточен только тогда, когда путь поиска абсолютный и начинается с /
. Сценарий cd / && find * ...
никогда не будет соответствовать -path '/media'
предложению.
К счастью, -inum
параметр может прийти на помощь. Номера инодов уникальны только для смонтированной файловой системы, поэтому для исключения /media
нам нужно идентифицировать кортеж, состоящий из файловой системы и номера инода.
Следующий (длинный) скрипт исключит /media
для вас все возможные ситуации, надеюсь, поймав достаточное количество пограничных случаев, чтобы быть полезным.
#!/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
решение2
Если вы хотите придерживаться "простого" использования find
(т. е. не несколько каталогов, никаких опций -H -L -D -P -O
) и вас устраивает использование -xdev
опции, попробуйте этот простой ответ. Это исключит все смонтированные файловые системы (например, даже $HOME
если они смонтированы отдельно).
Вы можете определить функцию bash find
так, чтобы она не вписывалась в другие файловые системы. Поместите это в свой ~/.bashrc
(предполагая, что вы используете bash
)
find () {
local path="${1}"
shift
command find "${path}" -xdev "${@}"
}
Объяснение: Нам нужно использовать функцию вместо псевдонима, потому что find
очень требователен к порядку своих аргументов. Должен path
быть первым аргументом. Поэтому мы сохраняем path
в локальной переменной и извлекаем ее из списка аргументов ( shift
). Затем мы запускаем исходный поиск command find
с путем и всеми оставшимися аргументами $@
. command
Перед , чтобы find
убедиться, что мы не получим рекурсивный вызов.
Для нового ~/.bashrc
файла вам сначала нужно указать его источник.
source ~/.bashrc
Затем вы можете использовать новую версию find
. Вы всегда можете проверить определение с помощью
> type find
find is a function
find ()
{
local path="${1}";
shift;
command find "${path}" -xdev "${@}"
}
решение3
( 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
)"
Существует скрипт-обертка, find
который вставит несколько аргументов, чтобы запретить реальному пользователю find
заглядывать внутрь /media/
, если какой-либо из его аргументов пути равен /
.
Приведенный выше сценарий состоит из двух частей: собственно сам сценарий(что является всем, что следует за<<""\n
) и в верхней части есть часть об установке, запускаемой один раз(то есть все, что находится между первой парой (
скобок )
).
установить
( 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
Установка требует немного вниманиянетдля успешного завершения, если только нет разумного шанса, что он сможет сделать это без прямого изменения чего-либо в вашей системе, кроме имени файла вашего $PATH
исполняемого find
файла - он хочет изменить его с /path/to/find
на /path/to/findcmd
и попытается сделать это, если /path/to/findcmd
его еще не существует. Если его тесты окажутся верными - и если у вас есть соответствующие разрешения на применение команд вообще - он переименует find
исполняемый файл и установит новый скрипт оболочки с именем find
на его место.
Установленный скрипт будет всегда после этого полагаться на переименованный findcmd
исполняемый файл, оставаясь там, где он его оставил.(так что вам, скорее всего, захочется сообщить об этом вашему менеджеру пакетов, если вы его используете)и каждый раз, когда он вызывается, он заменяет себя на $0cmd
called со всеми его аргументами после того, как он их просмотрит. Если вы не сделаете все необходимое, чтобы сделать установку постоянной, то в конечном итоге большинство менеджеров пакетов find
в какой-то момент перезапишут установленный скрипт новым обновленным двоичным файлом, и вы окажетесь там, где начали, за исключением того, что у вас также будет старый find
named findcmd
в системном ../bin
каталоге.
При наличии соответствующих разрешений и если ваша система не преподнесет вам никаких ненужных сюрпризов, весь скрипт должен быть автоматически установлен путем копирования и вставки в командную строку.(хотя вам нужно будет сделать дополнительный ВОЗВРАТ в конце). Если это не сработает, то, по крайней мере, попытка не должна принести вреда.
новый
#!/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
)}"
Мое первое и главное правило при написании скрипта-обертки:руки прочь. Если бы мне нужна была программа, я бы попытался написать ее, но поскольку у меня уже есть программа, достойная обертывания, я постараюсь позволить ей делать то, что она уже делает, беспрепятственно и как можно меньше изменять ее поведение, чтобы достичь моей конечной цели. Это означает, что я не должен делать ничего, что могло бы повлиять на ее среду выполнения каким-либо образом, который не имеет прямого отношения к цели обертывания. Поэтому я не устанавливаю переменные, не интерпретирую аргументы, не трогаю потоки ввода-вывода и не изменяю группу процессов обернутой программы или ее родительский pid. Во всех отношениях обертка должна быть как можно более временной и прозрачной.
Приведенный выше скрипт достигает этой цели, теперь в большей степени, чем раньше. Я не был удовлетворен раньше - особенно в отношении разрешений путей - но я считаю, что я справился с этим. Чтобы сделать это правильно, мне пришлось отслеживать [HLP]
состояние, чтобы я мог правильно сравнивать символические ссылки с тем /
, когда один из вариантов -H
или -L
был эффективен и -P
не отменял их. Если тест на ссылку проходит успешно, текущий аргумент проверяется на -ef
совпадение с соответствующим индексным дескриптором файла /
- что означает, что практически любое имя для /
будет работать(включать символические ссылки, когда -H
или -L
эффективны). Так что я чувствую себя лучше в этом отношении и настроил его на блокировку /proc
и /sys
исключение /dev
из /
поиска по умолчанию.
Что он делает особенно хорошо, так это избегает изменения любого из своих вызванных состояний перед передачей его в $0cmd
. Он демонстративно отказывается интерпретировать набор аргументов, который включает любую опцию, которую он не готов обработать, и в этих случаях передает весь набор в $0cmd
нетронутом виде, и поэтому, хотя в этих случаях он может не блокировать поиск пути, он также не влияет на find
поведение каким-либо другим образом. Именно по этой причине этот eval "exec wrapped_program $(arg-handler)"
метод является тем, который я предпочитаю больше всего для такого рода вещей.
верхний уровень
Фактически, как указано выше, на верхнем уровне весь скрипт оболочки сводится только к одной простой команде, которая говорит ему заменить себя другим исполняемым файлом. Любая работа, которая вообще выполняется, выполняется в подоболочке $(
подстановки команд )
, и все ее состояние — измененное или нет — полностью локализовано. Цель eval
всего этого — получить второй взгляд на аргументы скрипта без необходимости фактически влиять на них без необходимости — и именно для этого и предназначена эта оболочка.
Когда $(
подпрограмма команды )
выполнит свою работу, результирующая exec
команда 'd будет иметь вид:
exec "$0cmd" "${1}" ... ! \( -path "${[num]%/}/media/*" -prune \) "${2}" ...
...где все исходные аргументы (если таковые имеются) перечислены по порядку и номеру в их исходной и неизмененной форме(даже нулевые аргументы)в дополнение к шести( !
, \(
, -path
, "${[num]%/}/media/*"
, -prune
, \)
)вставки, набор которых происходит для каждого успешного / -ef "${num}"
теста во время сканирования arg. Иначе это будет просто:
exec "$0cmd" "${1}" "${2}" "${3}" "${4}" ...
...где все исходные аргументы упоминаются точно так же, без каких-либо вставок.
Таким образом, единственные два возможных изменения, которые эта оболочка может внести в среду своей упакованной цели, это:
Он изменяет имя процесса со своего собственного на свое имя +
cmd
. Так всегда бывает.Он может вводить шесть аргументов на каждое корневое совпадение в список тех, с которыми он был вызван.
Пока первое изменение считается приемлемым(хотя этого можно избежать), здесь есть единственная точка отказа относительно модификации поведения - и это то, является ли вставка аргумента допустимой. Любые ошибки, в противном случае связанные с вызовом, просто выходят за рамки и должны обрабатываться целью обертки, и эта обертка пытается заниматься своими делами.
arg-обработчик
В подстановке команд я сначала инициализирую переменные для отмены, поскольку ""
строка — это лучший способнетПри необходимости я сопоставляю путь. Затем я объявляю функцию chk()
, а затем вызываю ее для каждой итерации цикла while
, которая будет увеличиваться $i
на единицу для каждого из аргументов вызова скрипта. Она выведет каждое увеличение, $i
заключенное в кавычки и фигурные скобки, с предшествующим пробелом и знаком доллара, на stdout подкоманды:
printf ' "${'$i}\"
...
"${1}"
Он циклически перебирает вызовы, chk()
которые получают копию своих аргументов за итерацию, а затем shift
удаляет их до тех пор, пока не останется ни одного аргумента, и цикл завершается. chk()
сравнивает свой аргумент с его шаблонами и выполняет соответствующие действия:
(-maxdepth"$2") M= ;;
Если
$M
установлено , последний шаблон может соответствовать только нулевой строке, которая может не пройти последующие тесты сравнения путей своего блока, и поэтомуrt=$rt+!\(
и т. д. в этом случае никогда не происходит. В противном случае ничего не делается.Спецификация POSIX требует только
-[HL]
распознавания перед любыми[...path...]
операндами, а все остальные не определены. Вот что она говорит о том, какие[...path...]
операнды являются операндами, а какие — тестовыми операндами:Первый операнд и последующие операнды до, но не включая первый операнд, который начинается с a
−
, или является a!
или a(
, должны интерпретироваться как[...path...]
операнды. Если первый операнд начинается с a−
, или является a!
или a(
, поведение не определено. Каждый операнд пути является именем пути начальной точки в иерархии файлов.
([\(!]"$2"|-*"$2")
Текущий аргумент представляет собой одинарную
(
левую скобку или!
восклицательный знак, или он начинается с-*
тире, но не является им,-maxdepth
и последний шаблон был сопоставлен хотя бы один раз.printf %s ${1+%b}%.d "$rt" \\c 2>&- &&
- Записать значение
$rt
- если есть - в стандартный вывод подстановки команды, за которым следует успешная запись\c
%b
escape-последовательности нулевой длины или неудачное преобразование в%.d
ecimal того же самого и с той же длиной, если$1
не установлено и достигнут конец аргументов. Эта неудача завершит циклwhile
.
- Записать значение
chk(){ ${1+:} exit; }
- Если
printf
успешно, тоchk()
он сделал свою единственную попытку изменить какие-либо аргументы вообще. С этого момента циклwhile
может продолжать обрабатывать и выводить оставшиеся аргументы, ноchk()
не будет ничего делать, пока все они не будут исчерпаны, после чего он просто выполнитexit
подоболочку. И поэтому, как только второй шаблон совпадет хотя бы один раз, ни один из других больше не совпадет.
- Если
(-?*)
Текущий аргумент состоит как минимум из двух символов и начинается с тире. Этот шаблонболееисключающий, чем
-*"$2"
шаблон выше$O
установлен один раз, и поэтому он может соответствовать только до тех пор, пока не будет хотя бы одного аргументане делаетсопоставить его. Таким образом, все начальные параметры будут разделеныgetopts
и сопоставлены с[HPL]
. Если какой-либо начальный параметр не соответствует этому шаблону, функция рекурсивно вызывает себя, чтобы сопоставить шаблон выше и переопределитьchk()
. Таким образом, любая последовательность аргументов, которая явно не обрабатывается, просто передается дословно иfindcmd
делает с результатами все, что захочет.Для каждого начального варианта, который соответствует
-[HL]
переменной флага,$L
устанавливается значение null string. И для каждого из тех, которые соответствуют,-P
$L
этоunset
.
(${M-${O=?*}})
Первый встречающийся аргумент, который не совпадает,
-?*
вызовет$O
установку ' в?*
шаблон. После этого любой из первых двух шаблонов может соответствовать${O+$2}${2--}
. Если когда-либо-maxdepth$2
совпадает иM=
устанавливается в нулевую строку, этот шаблон никогда больше не сможет соответствовать другому ненулевому аргументу, и требуется только одно совпадение второго шаблона, чтобы прекратить все попытки сопоставления любого из них.Любой ненулевой аргумент, который появляется после первой последовательности
-[HLP]
опций и перед другим аргументом-*
or,[\(?!]
соответствует этому шаблону и проверяется на разрешение пути. Если$L
не установлено, то! ! -L "${L-$2}"
тест будет пройден, если$2
это символическая ссылка или недопустимое имя пути, но в противном случае неизменно терпит неудачу, поскольку ни одно имя пути не может соответствовать${L=}
нулевой строке.Только те аргументы, которые не прошли предыдущую проверку, проверяются на
!
соответствие отрицательному индексному дескриптору,/
и любой аргумент, который не прошел обе проверки, в результате получает$rt
значение самого себя и! \( -path "${[num]%/}/media/* -prune \)
строки, которая не записывается до тех пор, пока не совпадет второй шаблон или не будет достигнут конец аргументов, в зависимости от того, что произойдет раньше.