Linux findコマンドはディレクトリをスキップします。

Linux findコマンドはディレクトリをスキップします。

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

私はこのようなことをするときにディレクトリをsudo find / -name foo常にスキップする必要があることを確認したいと思います/media

コマンドに引数を渡したくない…デフォルトでは、ディレクトリを常にスキップするようにfindシステムを設定したいと思います。find/media

答え1

この場合、考慮すべき極端なケースがたくさんあります。find / -path '/media' -prune -o ...最初のアプローチは、検索パスが絶対パスで始まる場合にのみ十分です/。シーンは句と決してcd / && find * ...一致しません。-path '/media'

幸い-inum、パラメータを使用するとこの問題を解決できます。 inode番号はマウントされたファイルシステムごとに一意であるため、除外するにはファイル/mediaシステムとinode番号で構成されるタプルを識別する必要があります。

次の(長い)スクリプトはトラブルシューティングを/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)から取り出します。次に、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
)"

realがそのパスパラメータを見つけるのをfind防ぐために、いくつかのパラメータを挿入するラッパースクリプトがあります。find/media//

上記のスクリプトは2つの部分で構成されています。実際のスクリプト(これは次のすべてです。<<""\n)上部に1回実行インストールビットがあります。(これは最初の一致する括弧のペア間の(すべてです)。)

インストールする

(   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

設置には少し注意が必要ですいいえ$PATHd実行可能ファイルのファイル名を除いて、システムから何も直接変更せずに完了するための適切な機会がない場合は、正常に完了します。ファイル名findをに変更しようとし/path/to/find、まだ存在しない場合は/path/to/findcmd試してください。/path/to/findcmdテストが正しいことが判明し、コマンドを適用するための適切な権限がある場合は、実行可能findファイルの名前が変更され、findその場所に新しいシェルスクリプトがインストールされます。

以降にインストールされるスクリプトは、常にfindcmd名前が変更された実行可能ファイルに依存します。(したがって、ご使用の場合はパッケージマネージャにお知らせください)呼び出されるたびに、すべての$0cmd引数を調べてから呼び出され、すべての引数に自分自身を置き換えます。インストールを永続的にするために必要なアクションを取らないと、最終的にほとんどのパッケージマネージャはインストールされたスクリプトをある時点で新しくfind更新されたバイナリで上書きし、起動した場所に戻ります。findシステムディレクトリの古い名前です。findcmd../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を変更したりしません。すべてと同様に、ラッパーはできるだけ一時的で透明でなければなりません。

上記のスクリプトは、以前よりも多くの目標を達成します。以前は、特にルート解決では満足できなかったが、問題を解決したと信じていた。これを正しく実行するには、1つ以上のオプションが無効化されず、有効な時点とシンボリックリンクを[HLP]正しく比較できるように状態を追跡する必要があります。リンクテストに合格したら、現在の引数に同じファイルのinode一致があることを確認してください。これはほとんどすべての名前が機能することを意味します。/-H-L-P-ef//-H-Lまたは有効な場合はシンボリックリンクを含む)。だから私はこの内容について気分が良くなり、基本的にブロックして/proc検索/sysするように設定しました/dev/

特に良い点は、呼び出された状態を$0cmd。処理する準備ができていないオプションを含むパラメータセットを解釈することを明示的に拒否し、その場合はセット全体をそのまま渡します。したがって、このような場合、ルート検索を防ぐことはできませんが、他の方法で$0cmdは効果がありません。find指揮する。eval "exec wrapped_program $(arg-handler)"このようなことを処理するときに、この方法が私のお気に入りの方法である理由がまさにこれです。

トップレベル

実際、上記のように、最上位レベルでは、シェルスクリプト全体は単純なコマンドに過ぎません。そのコマンドは、自分自身を別の実行可能ファイルに置き換えるように指示します。実行されたすべての操作は$(コマンド置換サブシェルで実行され、)すべての状態(変更されたかどうか)は完全にローカライズされます。これの目的は、eval実際には不必要に影響を与えずにスクリプトのパラメータを見直すことです。これがこのラッパーの目的です。

$(sub コマンドが完了すると、)結果のexec'd コマンドは次のようになります。

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

...すべての元のパラメータ(存在する場合)は、元の変更されていない形式で順次数値的に参照されます。(空のパラメータでも)この6つを除く!、、、、、、、、、、)\(-path"${[num]%/}/media/*"-prune\)/ -ef "${num}"arg スキャン中に成功した各テストに対して一連の挿入が発生します。それ以外の場合は簡単です。

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

...すべての元のパラメータは、補間なしでまったく同じ方法で参照されます。

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

  • プロセス名を自分の名前から自分の名前に変更します。 +cmd。これは常に起こります。

  • 呼び出しの引数リストに、各ルート一致ごとに6つの引数を挿入できます。

最初の修正が許容可能とみなされる限り(これは避けられますが)、ここには、動作の修正に関して単一の障害点があります。つまり、パラメーター挿入が有効かどうかです。呼び出しに関連するすべてのエラーは範囲外であり、ラッパーターゲットによって処理されなければならず、ラッパーはその操作に注意を払うようにします。

引数ハンドラ

""コマンド置換では文字列が最善の方法なので、最初に設定されていない変数を初期化します。いいえ必要に応じてパスを一致させます。次に、関数を宣言し、chk()各呼び出し引数に対してスクリプトを1ずつ増やすループの反復ごとwhileに関数を呼び出します。コマンドサブの標準出力にスペースとドル記号の前に引用符と中括弧で囲まれた各増分を$i印刷します。$i

printf ' "${'$i}\"

...

 "${1}"

chk()繰り返すたびに引数のコピーを取得し、残りの引数がshiftなく、ループが完了するまで引数を削除するループから呼び出されます。chk()対応するパラメータをパターンと比較し、適切なアクションを実行します。

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

    • 設定すると、$M最後のパターンは空の文字列にのみ一致する可能性があり、これはそのブロックの後続のパス比較テストにのみ失敗する可能性があります。rt=$rt+!\(この場合、待機は発生しません。そうでなければ何もしないでください。

    • POSIX仕様では、-[HL]オペランドの前に認識する必要があり、[...path...]他のオペランドは指定されません。以下は、[...path...]オペランドとテストオペランドの説明です。

      最初のオペランドと後続のオペランド(aで始まる、またはaまたはaの最初のオペランドは含まれていません)は、オペランドとして解釈されます。最初のオペランドがaで始まる場合、またはaまたはaの場合、アクションは指定されません。各パスオペランドは、ファイル階層内の始点のパス名です。!([...path...]!(

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

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

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

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

      • printf成功すると、chk()パラメータを修正しようとする試みは一度だけ行われます。この時点から、whileループは引き続き残りの引数を処理して印刷できますが、すべての引数が使い果たされるまで何もしません。その時点ではサブシェルにchk()なります。exitしたがって、2番目のパターンが一度一致すると、他のパターンは再一致しません。
  • (-?*)

    • 現在のパラメータは2文字以上で、ダッシュで始まります。このパターンはもっと-*"$2"上記のパターンより$O1回設定すると排他的であるため、引数が1つ以上あるまでのみ一致できます。いいえ合わせてみてください。これにより、すべての初期オプションが分割され、getopts一致します[HPL]。初期オプションのいずれかがパターンに合わない場合、関数は自分自身を再帰的に呼び出し、上記のパターンと一致してオーバーライドしますchk()。このように明示的に処理されていないすべての引数シーケンスは単にそのまま渡され、findcmd結果に対して何もしません。

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

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

    • 一致しないパラメータが最初に発生すると、設定されたパターンが-?*トリガされます。その後、最初の2つのパターンのうちの1つが一致することがあります。一致し、空の文字列に設定されている場合、このパターンは空でない他の引数とは絶対一致しません。どちらか一方と一致しようとするすべての試みを停止するには、2番目のパターンと一致するものが1つだけ必要です。$O?*${O+$2}${2--}-maxdepth$2M=

    • -[HLP]最初のオプションシーケンスの後とは異なる引数または引数の前に表示-*される非NULL引数は、[\(?!]このパターンと一致し、パス検証をテストします。設定しないと、$Lシンボリックリンクまたは無効なパス名の場合はテストに合格します。! ! -L "${L-$2}"そうしないと、空の文字列に一致するパス名がないため、常に失敗します。$2${L=}

    • !前のテストに失敗したパラメータのみが負の inode と一致することを確認し、両方のテストに失敗したすべてのパラメータは、2 番目のパターンが一致するか、パラメータの終わりに達するまで設定された文字列で設定されます/。 (まず来るもの)が記録されます。$rt! \( -path "${[num]%/}/media/* -prune \)

関連情報