디렉토리 에 특정 네트워크 공유 폴더가 마운트되어 있습니다 /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
)"
경로 인수 중 하나라도 동일한지 확인하는 find
것을 금지하기 위해 몇 가지 인수를 삽입하는 래퍼 스크립트가 있습니다 .find
/media/
/
위 스크립트에는 두 부분이 있습니다. 실제 스크립트가 있습니다.(이것은 다음의 모든 것입니다<<""\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
설치 부분은 약간의 주의가 필요합니다~ 아니다시스템에서 아무것도 직접 수정하지 않고도 성공적으로 완료할 수 있지만 $PATH
'd 실행 파일의 파일 이름은 에서 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를 변경하지 않습니다. 모든 면에서 래퍼는 가능한 한 일시적이고 투명해야 합니다.
위의 스크립트는 이 목표를 이전보다 더 많이 달성합니다. 이전에는 특히 경로 해결과 관련하여 만족스럽지 않았지만 그 문제를 해결했다고 믿습니다. 이를 제대로 수행하려면 또는 옵션 중 하나가 유효하고 이를 부정하지 않은 경우 [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}" ...
...모든 원래 인수(있는 경우)는 원래의 변경되지 않은 형식으로 순서대로 번호별로 참조됩니다.(null 인수도 포함)여섯 가지 외에도( !
, \(
, -path
, "${[num]%/}/media/*"
, -prune
, \)
)/ -ef "${num}"
인수 스캔 중 성공적인 각 테스트에 대해 발생하는 삽입 세트입니다 . 그렇지 않으면 간단히 다음과 같습니다.
exec "$0cmd" "${1}" "${2}" "${3}" "${4}" ...
...여기서 모든 원래 인수는 전혀 삽입하지 않고 동일한 방식으로 참조됩니다.
따라서 이 래퍼가 래핑된 대상의 환경에 대해 수행할 수 있는 유일한 두 가지 수정은 다음과 같습니다.
프로세스 이름을 자체 이름에서 자체 이름으로 변경합니다. +
cmd
. 이런 일은 항상 발생합니다.호출된 항목 목록에 루트 일치 항목당 6개의 인수를 삽입할 수 있습니다.
첫 번째 변경이 허용 가능한 것으로 제공되는 한(피할 수는 있지만), 여기에 동작 수정과 관련하여 단일 실패 지점이 있습니다. 이는 인수 삽입이 유효한지 여부입니다. 호출과 관련된 모든 오류는 단순히 범위를 벗어나며 랩 대상에 의해 처리되어야 하며 이 래퍼는 해당 작업에 신경을 쓰려고 합니다.
인수 처리기
명령 대체 내에서 문자열 ""
이 가장 좋은 방법이기 때문에 먼저 설정 해제할 변수를 초기화합니다.~ 아니다필요할 때 경로를 일치시킵니다. 그런 다음 함수를 선언 하고 나중에 각 스크립트의 호출 인수에 대해 1씩 증가하는 루프 chk()
의 각 반복에 대해 함수를 호출합니다 . 공백과 달러 기호 앞에 따옴표와 중괄호로 묶인 각 증분을 명령 하위의 표준 출력에 인쇄합니다 .while
$i
$i
printf ' "${'$i}\"
...
"${1}"
chk()
반복마다 인수의 복사본을 가져오는 호출을 반복한 다음 shift
아무것도 남지 않고 루프가 완료될 때까지 처리합니다. chk()
인수를 패턴과 비교하고 적절한 조치를 취합니다.
(-maxdepth"$2") M= ;;
이 설정 되면
$M
마지막 패턴은 해당 블록의 후속 경로 비교 테스트에만 실패할 수 있는 null 문자열과만 일치할 수 있습니다.rt=$rt+!\(
그런 경우에는 절대 발생하지 않습니다. 그렇지 않으면 아무 일도 일어나지 않습니다.POSIX 사양은 피연산자
-[HL]
이전에만 인식되어야 하며[...path...]
다른 피연산자는 지정되지 않습니다.[...path...]
피연산자와 테스트 피연산자에 대한 설명은 다음과 같습니다 .첫 번째 피연산자와 a로 시작하거나 a 또는 a
−
인 첫 번째 피연산자를 포함하지 않는 후속 피연산자는 피연산자 로 해석됩니다 . 첫 번째 피연산자가 a 로 시작하거나 a 또는 a 인 경우 동작은 지정되지 않습니다. 각 경로 피연산자는 파일 계층 구조의 시작점에 대한 경로 이름입니다.!
(
[...path...]
−
!
(
([\(!]"$2"|-*"$2")
현재 인수는 단일
(
왼쪽 괄호 또는!
강타이거나 대시로 시작-*
하지만 대시가 아니며-maxdepth
마지막 패턴이 적어도 한 번 일치했습니다.printf %s ${1+%b}%.d "$rt" \\c 2>&- &&
- 의 값
$rt
(있는 경우)을 명령 대체의 표준 출력에 쓴 다음 이스케이프의 길이가 0인 쓰기에 성공하거나 , 설정되지 않고 인수의 끝이 있는 경우 동일한 길이의 ecimal\c
%b
로의 실패한 변환이 이어집니다. 도달했다. 이 실패로 인해 루프가 종료됩니다.%.d
$1
while
- 의 값
chk(){ ${1+:} exit; }
printf
성공하면 인수chk()
를 수정하려고 시도한 것뿐입니다. 이 시점부터 루프는while
나머지 인수를 계속 처리하고 인쇄할 수 있지만,chk()
이 모든 인수가 소진될 때까지 아무 작업도 수행하지 않으며 그 시점에서는exit
서브셸만 수행됩니다. 따라서 두 번째 패턴이 한 번이라도 일치하면 다른 패턴은 다시 일치하지 않습니다.
(-?*)
현재 인수는 2자 이상이며 대시로 시작됩니다. 이 패턴은더
-*"$2"
위의 패턴 보다 배타적$O
이며 한 번 설정되면 최소한 단일 인수가 나올 때까지만 일치할 수 있습니다.그렇지 않다일치시키세요. 이러한 방식으로 모든 초기 옵션은 으로 분할되어getopts
일치됩니다[HPL]
. 초기 옵션이 해당 패턴에 맞지 않으면 함수는 자신을 재귀적으로 호출하여 위의 패턴과 일치시키고 재정의합니다chk()
. 이러한 방식으로 명시적으로 처리되지 않은 모든 인수 시퀀스는 그대로 전달되고findcmd
결과에 대해 원하는 모든 작업을 수행합니다.-[HL]
플래그 변수 와 일치하는 각 초기 옵션에 대해$L
널 문자열로 설정됩니다. 그리고 일치하는 각각의 항목-P
$L
은 입니다unset
.
(${M-${O=?*}})
일치하지 않는 첫 번째 인수는 의 패턴 설정을 트리거
-?*
합니다 . 그 이후에는 처음 두 패턴 중 어느 것이든 일치할 수 있습니다 . 일치하고 null 문자열로 설정된 경우 이 패턴은 null이 아닌 다른 인수와 다시 일치할 수 없으며 두 번째 패턴의 단일 일치만 있으면 일치하려는 모든 시도를 중단할 수 있습니다.$O
?*
${O+$2}${2--}
-maxdepth$2
M=
-[HLP]
첫 번째 옵션 시퀀스 뒤, 다른 인수-*
또는 이 패턴과 일치하는 인수 앞에 발생하는 null이 아닌 인수[\(?!]
는 경로 확인을 위해 테스트됩니다.$L
가 설정되지 않은 경우 심볼릭 링크이거나 잘못된 경로 이름이면! ! -L "${L-$2}"
테스트가 통과되지만 , 그렇지 않으면 경로 이름이 nullstring과 일치할 수 없기 때문에 항상 실패합니다.$2
${L=}
이전 테스트에 실패한 인수만
!
부정된 inode 일치 여부를 확인/
하고 두 테스트 모두에 실패한 인수는 두 번째 패턴이 일치하거나 인수 끝에 도달할 때까지 기록되지 않는 문자열$rt
과 함께 자체 설정됩니다 .! \( -path "${[num]%/}/media/* -prune \)
먼저 온다.