O comando find do Linux ignora um diretório

O comando find do Linux ignora um diretório

Tenho certas pastas de compartilhamento de rede montadas no /mediadiretório.

Quero ter certeza de que, quando fizer algo assim, sudo find / -name foosempre pule o /mediadiretório.

Não quero passar parâmetro para o findcomando... Quero configurar meu sistema de forma que findsempre por padrão pule /mediadiretório.

Responder1

Há uma série de casos extremos que precisam ser considerados nesta situação. A primeira abordagem find / -path '/media' -prune -o ...é suficiente apenas quando o caminho de pesquisa é absoluto e começa com /. O cenário cd / && find * ...nunca corresponderá à -path '/media'cláusula.

Felizmente, o -inumparâmetro pode ajudar. Os números dos inodes são exclusivos apenas por sistema de arquivos montado, portanto, para excluir, /mediaprecisamos identificar a tupla que consiste no sistema de arquivos e no número do inode.

O script (longo) a seguir excluirá /mediapara você, esperançosamente capturando casos extremos suficientes para serem úteis.


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

Responder2

Se você deseja manter o uso "simples" de find(ou seja, sem vários diretórios, sem opções -H -L -D -P -O) e concorda em usar a -xdevopção, tente esta resposta simples. Isto excluirá todos os sistemas de arquivos montados (por exemplo, também $HOMEse forem montados separadamente).

Você pode definir uma função bash findde forma que ela não seja compatível com outros sistemas de arquivos. Coloque isso no seu ~/.bashrc(supondo que você esteja usando bash)

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

Explicação: Precisamos usar uma função em vez de um alias porque findé muito exigente quanto à ordem de seus argumentos. Deve pathser o primeiro argumento. Portanto, salvamos o pathem uma variável local e o retiramos da lista de argumentos ( shift). Em seguida, executamos o find original command findcom o caminho e todos os argumentos restantes $@. O commandin front of findgarante que não terminemos com uma chamada recursiva.

Com o novo ~/.bashrcarquivo, você precisa obtê-lo primeiro

source ~/.bashrc

Então, você pode usar a nova versão do find. Você sempre pode verificar a definição usando

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

Responder3

(   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
)"

Existe um script wrapper findque irá inserir alguns argumentos para proibir o real findde procurar /media/se algum de seus argumentos de caminho for igual /.

Existem duas partes do script acima: existe o script real(que é tudo o que segue<<""\n) e há o bit de instalação executado uma vez na parte superior(que é tudo entre o primeiro par de (parênteses )).

instalar

(   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

A parte de instalação exige um pouco de cuidadonãopara ser concluído com sucesso, a menos que haja uma chance razoável de fazê-lo sem modificar diretamente nada em seu sistema, exceto o nome do arquivo do seu executável $PATH'd find- ele deseja alterar isso de /path/to/findpara /path/to/findcmde fará a tentativa se /path/to/findcmdainda não existir. Se seus testes forem verdadeiros - e se você tiver permissões apropriadas para aplicar os comandos - ele renomeará o findexecutável e instalará um novo script de shell nomeado findem seu lugar.

O script instalado dependerá para sempre do findcmdexecutável renomeado permanecendo onde o deixou(então você provavelmente desejará informar seu gerenciador de pacotes sobre isso, se usar um)e cada vez que for invocado, ele se substituirá por $0cmdcall com todos os seus argumentos depois de dar uma olhada neles. Se você não fizer o que for necessário para tornar a instalação permanente, eventualmente a maioria dos gerenciadores de pacotes acabará sobrescrevendo o script instalado com um findbinário recém-atualizado em algum momento, e assim você estará de volta ao ponto de partida, exceto que você também terá um findnome mais antigo findcmdno ../bindiretório do sistema.

Dadas as permissões apropriadas e que seu sistema não garanta surpresas indevidas, todo o script deve ser auto-instalável com um copiar e colar em um prompt de shell(embora você precise fazer um RETURN extra no final). Se não funcionar dessa maneira, então, pelo menos, não deverá haver nenhum dano na tentativa.

recém-descoberto

#!/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
)}"

Minha regra principal ao escrever um script wrapper é:sem intervenção. Se eu precisasse de um programa, tentaria escrever um, mas como já tenho um programa que vale a pena embrulhar, tentarei deixá-lo fazer o que já faz sem impedimentos e modificar o mínimo possível seu comportamento para alcançar meu objetivo final. Isso significa que não devo fazer nada que possa afetar seu ambiente de execução de forma que não esteja diretamente relacionado ao propósito do wrap. Portanto, não defino variáveis, não interpreto argumentos, não toco em fluxos de E/S e não altero o grupo de processos do programa empacotado ou seu pid pai. Em todas as coisas, o invólucro deve ser tão transitório e transparente quanto possível.

O script acima atinge esse objetivo, mais agora do que antes. Eu não estava satisfeito antes - especialmente em relação às resoluções de caminhos - mas acredito que já resolvi isso. Para fazer isso corretamente, tive que rastrear [HLP]o estado para poder comparar corretamente os links simbólicos com /quando uma das opções -Hou -Lera eficaz e -Pnão os negava. Se o teste do link for aprovado, o argumento atual será verificado para uma -efcorrespondência de inode de arquivo igual /- o que significa que praticamente qualquer nome /funcionará(para incluir links simbólicos quando -Hou -Lforem eficazes). Então , me sinto melhor com essas coisas e configurei-o para bloquear /procee /sysde pesquisas por padrão./dev/

O que ele faz especialmente bem é evitar modificar qualquer estado chamado antes de passá-lo para $0cmd. Ele se recusa claramente a interpretar um conjunto de argumentos que inclui qualquer opção com a qual não está preparado para lidar e, nesses casos, passa todo o conjunto para $0cmdintocado e, portanto, embora nesses casos possa não bloquear a busca do caminho, também não afeta. findcomportamento de qualquer outra forma. É por esta razão que o eval "exec wrapped_program $(arg-handler)"método é o que mais prefiro para este tipo de coisas.

nível superior

Na verdade, como indicado acima, em seu nível superior, todo o shell-script equivale apenas a um único comando simples - que diz para ele se substituir por outro executável. Qualquer trabalho realizado é feito dentro do subshell $(de substituição de comando ), e todo o seu estado - modificado ou não - é totalmente localizado. O objetivo por trás evalde tudo é dar uma segunda olhada nos argumentos do script sem precisar afetá-los desnecessariamente - e é disso que se trata este wrapper.

Quando o $(comando sub )tiver feito seu trabalho, o execcomando 'd resultante será:

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

...onde todos os argumentos originais - se houver - são referenciados em ordem e por número em sua forma original e inalterada(mesmo argumentos nulos)além dos seis( !, \(, -path, "${[num]%/}/media/*", -prune, \))inserções, um conjunto das quais ocorre para cada / -ef "${num}"teste bem-sucedido durante a varredura arg. Caso contrário, será simplesmente:

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

...onde todos os argumentos originais são referenciados da mesma maneira, sem nenhuma inserção.

Portanto, as únicas duas modificações possíveis que este wrapper pode fazer no ambiente do seu destino empacotado são estas:

  • Ele altera o nome do processo do seu próprio para o seu nome +cmd. Isso sempre acontece.

  • Pode injetar seis argumentos por correspondência de raiz na lista daqueles com os quais foi chamado.

Desde que a primeira alteração seja considerada aceitável(embora seja evitável), há um único ponto de falha aqui em relação à modificação do comportamento - e é se a inserção do argumento é válida ou não. Quaisquer erros associados à invocação estão simplesmente fora do escopo e devem ser tratados pelo destino do wrapper, e esse wrapper tenta cuidar de seus negócios.

manipulador de argumentos

Dentro da substituição do comando, primeiro inicio vars para unset porque a ""string é a melhor maneira denãocombine um caminho quando necessário. Em seguida, declaro a chk()função e depois a chamo para cada iteração do whileloop que será incrementado $iem um para cada um dos argumentos de invocação do script. Ele imprimirá cada incremento $ientre aspas e colchetes precedido por um espaço e um cifrão no stdout do subcomando:

printf ' "${'$i}\"

...

 "${1}"

Ele faz um loop nas chamadas para chk()as quais obtém uma cópia de seus argumentos por iteração e, em seguida, shiftafasta-o até que não reste nenhum e o loop seja concluído. chk()compara seu argumento com seus padrões e toma as ações apropriadas:

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

    • Quando $Mé definido, o último padrão só pode corresponder a uma string nula que só pode falhar nos testes subsequentes de comparação de caminho do seu bloco, e assimrt=$rt+!\(e assim por diante nunca ocorre nesse caso. Caso contrário, nada será feito.

    • A especificação POSIX só precisa -[HL]ser reconhecida antes de qualquer [...path...]operando, e quaisquer outros não são especificados. Aqui está o que diz sobre quais são [...path...]operandos e quais são operandos de teste:

      O primeiro operando e os operandos subsequentes até, mas não incluindo, o primeiro operando que começa com a , ou é a ! ou a (, devem ser interpretados como [...path...]operandos. Se o primeiro operando começar com a , ou for a ! ou a (, o comportamento não será especificado. Cada operando de caminho é um nome de caminho de um ponto inicial na hierarquia de arquivos.

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

    • O argumento atual é um único (parêntese esquerdo, ou um !estrondo, ou começa com um -*travessão, mas não é -maxdepthe o último padrão foi correspondido pelo menos uma vez.

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

      • Escreva o valor de $rt- se houver - no padrão de substituição do comando, seguido por uma gravação de comprimento zero bem-sucedida do \c %bescape ou uma conversão falhada para %.decimal do mesmo e com o mesmo comprimento se $1não estiver definido e o final dos argumentos tiver sido alcançado. Essa falha encerrará o whileloop.
    • chk(){ ${1+:} exit; }

      • Se printffor bem sucedido, então chk()terá feito a sua única tentativa de modificar quaisquer argumentos. Deste ponto em diante, o whileloop pode continuar a manipular e imprimir o restante dos argumentos, mas chk()não fará nada até que todos eles estejam esgotados, momento em que será apenas exito subshell. E assim, uma vez que o segundo padrão corresponda pelo menos uma vez, nenhum dos outros corresponderá novamente.
  • (-?*)

    • O argumento atual tem pelo menos dois caracteres e começa com um travessão. Este padrão émaisexclusivo do que o -*"$2"padrão acima dele $Oé definido uma vez e, portanto, só pode corresponder até pelo menos um único argumentonãocombine com isso. Desta forma, todas as opções iniciais serão divididas getoptse comparadas [HPL]. Se alguma opção inicial não se enquadrar nesse padrão, a função chama a si mesma recursivamente para corresponder ao padrão acima dela e redefinir chk(). Desta forma, qualquer sequência arg que não seja explicitamente tratada é meramente passada literalmente e findcmdfaz o que quiser com os resultados.

    • Para cada opção inicial que corresponda -[HL]à variável flag $Lé definida como a string nula. E para cada um daqueles que correspondem -P $Lé unset.

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

    • O primeiro argumento que não corresponder -?*irá acionar $Oa definição do ?*padrão. Depois disso, qualquer um dos dois primeiros padrões pode corresponder ${O+$2}${2--}. Se ever -maxdepth$2for correspondido e M=definido como a string nula, esse padrão nunca mais poderá corresponder a outro argumento não nulo, e apenas uma única correspondência do segundo padrão será necessária para cessar todas as tentativas de correspondência de qualquer um deles.

    • Qualquer argumento não nulo que ocorre após a primeira sequência de -[HLP]opções e antes de outro argumento -*ou [\(?!]corresponde a este padrão e é testado para resolução de caminho. Se $Lnão estiver definido, o ! ! -L "${L-$2}"teste será aprovado se $2for um link simbólico ou um nome de caminho inválido, mas caso contrário, invariavelmente falhará porque nenhum nome de caminho pode corresponder à ${L=}cadeia nula.

    • Somente aqueles argumentos que falham no teste anterior são verificados quanto a uma !correspondência de inode negada /e qualquer argumento que falha em ambos os testes resulta em $rtser definido para si mesmo mais a ! \( -path "${[num]%/}/media/* -prune \)string que não é escrita até que o segundo padrão corresponda ou o final dos argumentos seja alcançado, o que for vem primeiro.

informação relacionada