디렉토리에 일부 네트워크 공유 폴더가 마운트되어 있습니다 /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/
/
위의 스크립트는 두 부분으로 구성됩니다. 실제 스크립트(이것은 다음의 모든 것입니다.<<""\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)"
이런 종류의 일을 처리할 때 이 방법이 제가 가장 좋아하는 방법인 이유가 바로 이것입니다 .
최상위 수준
실제로 위에서 언급한 것처럼 최상위 수준에서 전체 셸 스크립트는 간단한 명령에 지나지 않습니다. 해당 명령은 자신을 다른 실행 파일로 바꾸라고 지시합니다. 수행된 모든 작업은 $(
명령 대체 subshell 에서 수행되며 )
모든 상태(수정 여부)는 완전히 현지화됩니다. 이것의 목적은 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}" ...
...모든 원래 매개변수는 보간 없이 정확히 동일한 방식으로 참조됩니다.
따라서 이 래퍼가 래핑된 대상의 환경에 대해 수행할 수 있는 수정 사항은 두 가지뿐입니다.
프로세스 이름을 자신의 이름에서 자신의 이름으로 변경합니다. +
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
마지막 패턴이 적어도 한 번 일치했습니다.printf %s ${1+%b}%.d "$rt" \\c 2>&- &&
- - 값이 있는 경우 명령 대체의 표준 출력에 - 값을 기록하고
$rt
, 성공 시 이스케이프된 길이가 0인 쓰기가 뒤따릅니다. 또는 설정되지 않고 인수 끝이 설정된 경우\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=?*}})
일치하지 않는 매개변수가 처음 발생하면 설정되는 패턴이
-?*
트리거됩니다 . 그 후에는 처음 두 패턴 중 하나가 일치할 수 있습니다 . 일치하고 빈 문자열로 설정된 경우 이 패턴은 비어 있지 않은 다른 인수와 절대 일치하지 않으며 둘 중 하나와 일치하려는 모든 시도를 중지하려면 두 번째 패턴과 일치하는 항목이 하나만 필요합니다.$O
?*
${O+$2}${2--}
-maxdepth$2
M=
-[HLP]
첫 번째 옵션 시퀀스 뒤와 다른 또는 인수 앞에-*
나타나는 null이 아닌 인수는[\(?!]
이 패턴과 일치하고 경로 확인을 테스트합니다. 설정하지 않으면$L
심볼릭 링크이거나 유효하지 않은 경로 이름인 경우 테스트가 통과하지만,! ! -L "${L-$2}"
그렇지 않으면 빈 문자열과 일치하는 경로 이름이 없기 때문에 항상 실패합니다.$2
${L=}
!
이전 테스트에 실패한 매개변수만 음의 inode 와 일치하는지 확인하고 , 두 테스트에 모두 실패한 모든 매개변수 는 두 번째 패턴이 일치하거나 매개변수 끝에 도달할 때까지 설정된 문자열 과 함께 설정/
됩니다 . (먼저 오는 것)이 기록됩니다.$rt
! \( -path "${[num]%/}/media/* -prune \)