Linux find 指令跳過目錄

Linux find 指令跳過目錄

我在目錄中安裝了某些網路共用資料夾/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對其參數的順序非常挑剔。 Thepath必須是第一個參數。因此,我們將 保存path在局部變數中並將其從參數清單中彈出 ( shift)。然後我們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
)"

有一個包裝腳本find將插入一些參數來禁止 realfind查找/media/其任何路徑參數 equal /

上面的腳本有兩個部分:實際的腳本(這是以下所有內容<<""\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

安裝有點需要小心不是成功完成,除非有合理的機會它可以在不直接修改系統上的任何內容的情況下完成,但你的$PATHdfind可執行文件的文件名 - 它想要將其更改為/path/to/find,並且如果尚不存在,/path/to/findcmd則會進行嘗試。/path/to/findcmd如果它的測試證明是正確的 - 並且如果您具有應用命令的適當權限 - 它將重命名find可執行檔並安裝一個新的 shell 腳本find來代替它。

此後,安裝的腳本將永遠依賴findcmd保留在原處的重命名的可執行文件(因此,如果您使用的話,您可能需要通知您的套件管理器)每次呼叫它時,它都會$0cmd在查看所有參數後將其自身替換為 Called 及其所有參數。如果您不採取任何必要措施來使安裝永久化,那麼最終大多數軟體包管理器都會find在某個時候用新更新的二進位檔案覆蓋已安裝的腳本,因此您將回到開始的地方,除了您還將在系統目錄中擁有一個較舊的find名稱。findcmd../bin

給予適當的權限並且您的系統不會保證任何不當的意外,整個腳本應該可以透過複製貼上到 shell 提示符號中來自行安裝(儘管你最後需要做一個額外的 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]狀態,以便我可以正確地將符號連結與/其中一個-H-L兩個選項有效且-P不會否定它們的時間進行比較。如果連結測試通過,則檢查當前參數是否有-ef相同的檔案 inode 匹配/- 這意味著幾乎任何名稱/都可以使用-H(當或-L有效時包含符號連結)。所以我對這些東西感覺更好,並且我已將其設置為預設阻止/proc/sys/dev搜尋。/

它特別擅長的是避免在將其傳遞給 之前修改任何被呼叫的狀態$0cmd。它明確拒絕解釋包含任何它不准備處理的選項的參數集,並且在這些情況下將整個集傳遞為$0cmd未觸及的,因此,雖然在這些情況下它可能不會阻止路徑搜索,但也不會影響find的任何其他方式的行為。正是由於這個原因,該eval "exec wrapped_program $(arg-handler)"方法是我最喜歡處理這類事情的方法。

頂層

事實上,如上所述,在其頂層,整個 shell 腳本僅相當於一個簡單的命令 - 該命令告訴它用另一個可執行檔替換自身。任何完成的工作都是在$(命令取代子)shell 中完成的,並且其所有狀態(修改與否)都是完全本地化的。背後的目的eval是再次查看腳本的參數,而不需要實際不必要地影響它們 - 這就是這個包裝器的全部內容。

$(命令 sub)完成其工作後,生成的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腳本的每個呼叫參數增加 1。它將列印$i每個由引號和大括號括起來的增量,前面有一個空格和一個美元符號到命令子的標準輸出:

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則將失敗轉換為循環。%.d$1while
    • chk(){ ${1+:} exit; }

      • 如果printf成功,則將chk()進行一次且唯一一次嘗試修改任何參數。從此時開始,while循環可能會繼續處理和列印其餘的參數,但chk()不會執行任何操作,直到所有這些參數都用盡為止,此時它將只是exit子 shell。因此,一旦第二個模式匹配一​​次,其他模式就不會再匹配。
  • (-?*)

    • 目前參數至少有兩個字符,並以破折號開頭。這個圖案是更多的-*"$2"它上面的模式一旦$O設定是獨佔的,因此它只能匹配,直到至少有一個參數匹配它。這樣,所有初始選項都將被拆分getopts並與 進行匹配[HPL]。如果任何初始選項不適合該模式,則函數將遞歸呼叫自身以匹配其上方的模式並重新定義chk()。透過這種方式,任何未明確處理的 arg 序列僅逐字傳遞,並對findcmd結果執行任何操作。

    • -[HL]對於與標誌變數相符的每個初始選項,$L設定為空字串。對於每個匹配的-P $Lunset.

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

    • 第一個出現的不匹配的參數-?*將觸發$O被設定為?*模式。此後,前兩個模式中的任何一個都可以匹配${O+$2}${2--}。如果ever-maxdepth$2匹配並M=設定為空字串,則此模式永遠不會再匹配另一個非空參數,並且只需要第二個模式的一次匹配即可停止匹配其中任何一個的所有嘗試。

    • -[HLP]在第一個選項序列之後和另一個-*或參數之前出現的任何非空參數都[\(?!]與此模式匹配,並測試路徑解析。如果$L未設置,則如果是符號連結或無效路徑名,則! ! -L "${L-$2}"測試將通過,但否則總是會失敗,因為沒有路徑名可以與空字串匹配。$2${L=}

    • 僅檢查那些未通過前一測試的參數是否與!否定 inode 匹配,/並且任何未通過兩次測試的參數都會導致被$rt設置為其自身加上! \( -path "${[num]%/}/media/* -prune \)直到第二個模式匹配或到達參數末尾(以兩者為準)之前不會寫入的字串首先。

相關內容