Команда find в Linux пропускает каталог

Команда find в Linux пропускает каталог

У меня в каталоге смонтированы некоторые сетевые папки /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исполняемый файл, оставаясь там, где он его оставил.(так что вам, скорее всего, захочется сообщить об этом вашему менеджеру пакетов, если вы его используете)и каждый раз, когда он вызывается, он заменяет себя на $0cmdcalled со всеми его аргументами после того, как он их просмотрит. Если вы не сделаете все необходимое, чтобы сделать установку постоянной, то в конечном итоге большинство менеджеров пакетов findв какой-то момент перезапишут установленный скрипт новым обновленным двоичным файлом, и вы окажетесь там, где начали, за исключением того, что у вас также будет старый findnamed 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 %bescape-последовательности нулевой длины или неудачное преобразование в %.decimal того же самого и с той же длиной, если $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 \)строки, которая не записывается до тех пор, пока не совпадет второй шаблон или не будет достигнут конец аргументов, в зависимости от того, что произойдет раньше.

Связанный контент