Linuxのfindコマンドがディレクトリをスキップする

Linuxのfindコマンドがディレクトリをスキップする

ディレクトリに特定のネットワーク共有フォルダーがマウントされています/media

次のような操作を行うときは、sudo find / -name foo必ず/mediaディレクトリをスキップするようにしたいです。

コマンドにパラメータを渡したくありません...デフォルトで常にディレクトリをスキップするfindようにシステムを構成したいと思います。find/media

答え1

この状況では、考慮する必要があるエッジ ケースがいくつかあります。最初のアプローチは、find / -path '/media' -prune -o ...検索パスが絶対で、 で始まる場合にのみ十分です/。シナリオは、句cd / && find * ...に一致することはありません-path '/media'

幸いなことに、-inumパラメータが役に立ちます。i ノード番号はマウントされたファイルシステムごとに一意であるため、除外するには、/mediaファイルシステムと i ノード番号で構成されるタプルを識別する必要があります。

次の (長い) スクリプトは/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個別にマウントされている場合も)。

find他のファイルシステムに侵入しないようにbash関数を定義することもできます。これを~/.bashrc( を使用していると仮定してbash

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

find説明: は引数の順序に非常に厳しいため、エイリアスではなく関数を使用する必要があります。 はpath最初の引数でなければなりません。したがって、 をpathローカル変数に保存し、引数リスト ( ) からポップします。次に、パスと残りのすべての引数を使用してshift元の find を実行します。の前の により、再帰呼び出しが行われなくなります。command find$@commandfind

新しい~/.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
)"

には、パス引数のいずれかが と等しい場合にfindreal がfind検索するのを禁止する引数をいくつか挿入するラッパー スクリプトがあります。/media//

上記のスクリプトには2つの部分があります。実際のスクリプトと(これは以下のすべてです<<""\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'dfind実行可能ファイルのファイル名以外のシステム上のものを直接変更せずに実行できる合理的な可能性がある場合を除き、正常に完了することはできません。ファイル名を/path/to/findからに変更/path/to/findcmdし、まだ存在しない場合は変更を試みます/path/to/findcmd。テストが真であることが証明され、コマンドを適用するための適切な権限がある場合は、実行可能ファイルの名前を変更し、その代わりにfindという名前の新しいシェル スクリプトをインストールします。find

インストールされたスクリプトは、その後、名前が変更されたfindcmd実行ファイルが元の場所に残っていることに永久に依存することになります。(そのため、パッケージ マネージャーを使用している場合は、このことをパッケージ マネージャーに通知する必要があります)$0cmdそして、それが呼び出されるたびに、その引数をすべて調べた後、その引数とともに calledに置き換えられます。インストールを永続的にするために必要なことはすべて行わないと、最終的にはほとんどのパッケージ マネージャーが、インストールされたスクリプトをある時点で新しく更新されたバイナリで上書きすることになり、システムのディレクトリに古いnamedfindも存在することを除けば、開始時の状態に戻ります。findfindcmd../bin

適切な権限があり、システムに過度の驚きがない場合は、スクリプト全体をシェルプロンプトにコピー&ペーストするだけでインストールできるはずです。(ただし、最後に追加の RETURN を実行する必要があります)もしそれがうまくいかなかったとしても、少なくともその試みで害が及ぶことはないはずです。

新しい発見

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

ラッパー スクリプトを書くときの私の第一のルールは次のとおりです。触るなプログラムが必要であれば自分で書いてみるつもりですが、ラップする価値のあるプログラムはすでにあるので、そのプログラムが既に行っていることを妨げずに実行させ、最終目標を達成するためにその動作をできるだけ変更しないようにします。つまり、ラップの目的に直接関係しない方法で実行環境に影響を与える可能性のあることは何もしないということです。つまり、変数を設定したり、引数を解釈したり、I/O ストリームに触れたり、ラップされたプログラムのプロセス グループや親 PID を変更したりしません。すべての点で、ラッパーは可能な限り一時的かつ透過的である必要があります。

上記のスクリプトは、以前よりもこの目標を達成しています。以前は特にパス解決に関して満足していませんでしたが、その点に対処できたと思います。これを適切に行うには、またはオプションのいずれかが有効で、それらを否定していない場合に[HLP]、シンボリックリンクを正しく比較できるように状態を追跡する必要がありました。リンクテストが合格すると、現在の引数が等しいファイルinodeに一致するかどうかがチェックされます。つまり、実質的には の任意の名前が機能します。/-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}" ...

...元の引数(もしあれば)はすべて、元の変更されていない形式で順番に番号で参照されます。(引数が null であっても)6つに加えて!、、、、、、\(-path"${[num]%/}/media/*"-prune\)挿入のセットは、/ -ef "${num}"引数スキャン中に成功したテストごとに発生します。それ以外の場合は、単純に次のようになります。

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

...元の引数はすべて、まったく挿入されることなくまったく同じ方法で参照されます。

したがって、このラッパーがラップされたターゲットの環境に対して実行できる変更は、次の 2 つだけです。

  • プロセス名を独自の名前からその名前に変更します +cmd。これはいつも起こることです。

  • ルート一致ごとに 6 つの引数が、呼び出された引数のリストに挿入される場合があります。

最初の変更が受け入れられる限り(避けられるとはいえ)、動作の変更に関して、ここでは単一の障害点があります。それは、引数の挿入が有効かどうかです。呼び出しに関連するその他のエラーは、単に範囲外であり、ラップ ターゲットによって処理される必要があり、このラッパーは自分のビジネスに専念しようとします。

引数ハンドラ

コマンド置換では、まず変数を初期化してunsetにします。""文字列はない必要に応じてパスを一致させます。次に関数を宣言し、その後、スクリプトの呼び出し引数ごとに 1 ずつ増加するループchk()の各反復で呼び出します。各増分は引用符と中括弧で囲まれ、スペースとドル記号が前に付き、コマンド サブの stdout に出力されます。while$i$i

printf ' "${'$i}\"

...

 "${1}"

呼び出しをループし、chk()反復ごとに引数のコピーを取得し、shift引数がなくなるまでコピーを取り出してループを完了します。chk()引数をパターンと比較し、適切なアクションを実行します。

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

    • が設定されている場合、$M最後のパターンはヌル文字列にのみ一致し、そのブロックの後続のパス比較テストに失敗する可能性があるので、rt=$rt+!\(その場合、そのようなことは決して起こりません。それ以外の場合は何も行われません。

    • POSIX 仕様では、オペランド-[HL]の前に認識されることのみが要求されており[...path...]、その他は指定されていません。どれが[...path...]オペランドでどれがテスト オペランドであるかについては次のように規定されています。

      最初のオペランドと、 で始まるか、 または で! ある最初のオペランドまでの後続のオペランドは、オペランド(として解釈されます[...path...]。最初のオペランドが で始まるか、 または である! 場合(、動作は未指定です。各パス オペランドは、ファイル階層内の開始点のパス名です。

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

    • 現在の引数は単一の(左括弧、または!感嘆符、またはダッシュで始まっているが-*ダッシュではなく-maxdepth、最後のパターンが少なくとも 1 回一致しています。

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

      • - が存在する場合 -の値を$rtコマンド置換の標準出力に書き込み、続いてエスケープの長さゼロの書き込みに成功するか、または が設定されておらず引数の終わりに達した場合は、同じ長さの同じの ecimal\c %bへの変換に失敗します。この失敗によりループが終了します。%.d$1while
    • chk(){ ${1+:} exit; }

      • printfが成功した場合、chk()引数を変更する唯一の試みが行われたことになります。この時点から、ループはwhile残りの引数の処理と出力を継続しますが、chk()これらがすべて使い果たされるまで何も行わず、使い果たされた時点でexitサブシェルのみを実行します。したがって、2 番目のパターンが 1 回でも一致すると、他のパターンは二度と一致しなくなります。
  • (-?*)

    • 現在の引数は少なくとも2文字で、ダッシュで始まります。このパターンはもっと-*"$2"一度設定されると、その上のパターンよりも排他的になり$O、少なくとも1つの引数が一致するまでしか一致しません。しないに一致します。このようにして、すべての初期オプションは で分割されgetopts、 と照合されます[HPL]。初期オプションのいずれかがそのパターンに適合しない場合は、関数は自分自身を再帰的に呼び出して、その上のパターンに一致させ、 を再定義しますchk()。このように、明示的に処理されない引数シーケンスは、そのまま渡され、findcmd結果に対して何でも行います。

    • -[HL]フラグに一致する各初期オプションに対して、変数$Lは null 文字列に設定されます。また、一致する各オプションに対してはが設定され-P $Lますunset

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

    • 一致しない最初の引数により、がパターンに設定されます。その後、最初の 2 つのパターンのいずれかが に一致する可能性があります-?*。が一致し、 が null 文字列に設定された場合、このパターンは別の null でない引数に再び一致することはなく、2 番目のパターンが 1 回一致するだけで、それらのいずれかに一致するすべての試行が停止されます。$O?*${O+$2}${2--}-maxdepth$2M=

    • -[HLP]最初のオプション シーケンスの後、別の-*または引数の前に出現する null 以外の引数は、[\(?!]このパターンに一致し、パス解決のためにテストされます。 が$L設定されていない場合、 がシンボリック リンクまたは無効なパス名であれば! ! -L "${L-$2}"テストは成功します$2が、それ以外の場合は null 文字列に一致するパス名がないため、常に失敗します${L=}

    • 前のテストに失敗した引数のみが、!否定された inode の一致に対してチェックされ/、両方のテストに失敗した引数は、 2 番目のパターンが一致するか引数の終わりに達するまで書き込まれない文字列$rtと引数自体に設定されます! \( -path "${[num]%/}/media/* -prune \)(どちらか早い方)。

関連情報