find
파일에서 디렉토리 목록을 제외하기 위해 실행하려고 합니다 . 이 파일은 다른 명령에서도 사용되므로 파일 형식을 고수했습니다. 즉, 각 줄에는 "as is"라는 디렉터리 이름과 슬래시가 포함되어 있습니다.
My Files/
xargs와 같은 다양한 접근 방식을 시도했지만 제대로 작동하지 못했지만 마침내 블랙리스트를 전처리하여 다음과 같은 매개변수를 생성하기로 결정했습니다.
find . -type f $(cat .blacklist.txt | while read -r line; do printf "! -path './%s*' -prune " "$line"; done)
그러나 블랙리스트에서 공백이 포함된 디렉터리 이름을 발견할 때마다 다음 오류가 발생합니다.
find: paths must precede expression: `<Last Part Of Name>/*''
무슨 일이 일어나고 있는지 알아내려고 노력 했는데 set -x
, 디렉토리 이름의 일부가 개별적으로 참조되고 있는 것 같습니다. 예를 들어, 목록에 "My Files"라는 디렉터리 이름이 있다고 가정합니다. set -x
파일에서 다음 특정 줄의 출력을 볼 수 있습니다 .
++ printf '! -path '\''./%s*'\'' -prune ' 'My Files/'
지금까지는 너무 좋다고 생각해요. 그러나 최종 조립된 명령에서 출력을 보면 다음과 같습니다.
'!' -path ''\''./My' 'Files/*'\''' -prune
문제는 명백합니다. 디렉토리 이름을 두 부분으로 나누려고 열심히 노력하고 있다는 것입니다! 하지만 왜 이런 일을 하는지 이해가 되지 않습니다. 나는 다음과 같은 몇 가지 변형을 시도했습니다.
printf "! -path './%s*' -prune " "$line"
나는 어떻게든 printf에서 나올 때 경로의 결과 출력이 인용되도록 해야 한다고 생각합니다. 그러나 나는 그것들을 모두 시도했지만 그 중 아무것도 작동하지 않습니다.
printf "! -path \"./%s*\" -prune " "$line"
printf "! -path "'./$line*'" -prune "
printf "! -path '"./$line*"' -prune "
그러나 이 중 어느 것도 디렉토리 이름의 개별 단어가 분할되는 것을 방지하지 못합니다.
나는 또한 이것을 시도했습니다 :
printf "! -path ./%s* -prune " "$line"
이렇게 하면 디렉터리 이름이 분할되는 것을 방지할 수 있지만 더 이상 참조되지 않으므로 해당 경로 아래의 모든 하위 디렉터리로 확장됩니다. 이는 잘못된 것이며 이제 그 중 하나만 필요한 하위 디렉터리가 여러 개 있으므로 명령도 중단됩니다.
%s
별도의 매개변수를 사용하고 매개변수 printf
를 %s
직접 사용해도 문제가 되지 않는 것 같습니다.$line
답변1
그리고 디렉터리 이름의 일부가 개별적으로 참조되는 것으로 보입니다.
나는 그렇게 말하지 않을 것이다. ! -path './My Files*' -prune
예를 들어 명령 대체에서 인쇄 하면 토큰화 단계에서 따옴표와 같은 구문을 처리하지 않고 공백 문자(또는 이미 알고 있는 모든 설정)만 보기 때문에 , !
, -path
, './My
로 토큰화됩니다 . 이는 glob 역할도 하지만 작은따옴표로 끝나는 파일 이름이 없을 수도 있습니다.Files*'
-prune
IFS
Files*'
출력에 표시되는 인용문은 set -x
Bash가 출력을 셸에 대한 입력으로 표시할 수 있도록 유효하게 만들기 때문에 존재합니다. 이스케이프된 인용문은 에서 인쇄한 인용문입니다 printf
.
!
여기서는 , -path
, ./My Files*
를 -prune
의 다른 매개변수로 생성해야 합니다 find
. 한 가지 방법은 매개변수를 배열로 수집하는 것입니다.
#!/bin/bash
args=()
filename='./My Files'
args+=( ! -path "$filename*" -prune )
# etc.
find . -type f "${args[@]}"
하지만 정말 필요할 수도 있어요
find . -type f ! -path './somedir/*' ! -path './otherdir/*'
(아니요 -prune
, 나열된 디렉토리를 포함하여 전체 트리를 탐색하고 그 안의 모든 항목을 무시하십시오.) 또는 Stéphane Chazelas가 답변에서 보여주는 것처럼
find . \( -path ./somedir -o -path ./otherdir \) -prune -o -type f -print
(나열된 디렉토리에 들어갈 수도 없습니다. ()
쉘을 탈출해야 합니다.)
그리고제외된 이름에 find
패턴 일치에 고유한 문자( *?[
)가 포함될 수 있는 경우 백슬래시 및 백슬래시를 사용하여 이러한 문자를 이스케이프해야 합니다.
이전 패턴을 사용하고 이스케이프를 무시하는 경우:
#!/bin/bash
args=(-type f)
while read -r filename; do
args+=( ! -path "./$filename/*" )
done < excluded.txt
find . "${args[@]}"
더 나은 접근 방식은 다음과 같이 좀 더 복잡합니다.
#!/bin/bash
args=()
first=1
while read -r escaped; do
if [ "$first" != 1 ]; then
args+=( -o )
fi
args+=( -path "./$escaped" )
first=0
done < <(sed -e 's/[[?*\]/\\&/g' < excluded.txt)
find . \( "${args[@]}" \) -prune -o -type f -print
(여기서 또한 프로세스 대체( <(cmd...)
)를 사용하여 탈출을 위해 sed를 통해 파일 이름 목록을 파이프합니다)
배열을 만들 때 중요한 점은 추가 따옴표를 추가하지 않고 명령에 입력한 것과 똑같이 매개변수를 할당에 입력하는 것입니다. 그런 다음 배열로 작업할 때는 구문에만 집중해야 하며 "${args[@]}"
따옴표에 주의해야 합니다.
답변2
/
일반 파일을 찾는 것이 목적인 경우 경로와 디렉토리의 파일을 나열하는 각 줄을 건너뛰십시오 .blacklist.txt
. 그러면 파일에 다음이 포함됩니다.
my dir/
my [other] dir?/sub dir/
예를 들어, 다음 매개변수를 사용하여 find를 호출해야 합니다.
find
.
(
-path
./my dir
-o
-path
./ my \[other\] dir\?/sub dir
([
그리고 이스케이프 처리됩니다. 그렇지 않으면 s에 의해 특별히 처리?
됩니다 . 대부분의 구현에서는 이스케이프가 필요하지 않지만 해를 끼치지는 않습니다)find
-path
]
find
)
-prune
# 위에서 일치하는 항목을 다듬습니다.-o
# 다른 것:-type
f
# "일반" 유형의 파일-print
따라서 각 행마다 다음이 필요합니다.
- 후행 제거
/
- 특수 문자 이스케이프
-path
:\?*[]
- 접두사
./
-path
한 줄 앞에 추가
그런 다음 각각 사이에 인수를 배치 -o
하고 결과 행을 목록으로 수집하여 find
.
readarray -t
이 분할을 수행하려면 bash 쉘에서 zsh
및 매개변수 확장 플래그를 사용하여 수행 할 수 있습니다 f
. 주변 따옴표를 생략하고 이전처럼 분할+glob을 사용할 수 있지만 먼저 개행 문자로만 설정하고 원하지 않는 glob 부분을 비활성화 $(...)
해야 합니다 (zsh에서는 수행되지 않음).$IFS
set -o noglob
존재하다 bash
:
readarray -t args < <(
sed 's|/$||
s|[][\\?*]|\\&|g
s|^|./|
1!i\
-o
i\
-path' .blacklist.txt
)
find . '(' "${args[@]}" ')' -prune -o -type f -print
zsh에서:
find . '(' ${(f)"$(
sed 's|/$||
s/[][\\?*]/\\&/g
s|^|./|
1!i\
-o
i\
-path' .blacklist.txt)"} ')' -prune -o -type f -print
zsh
b
glob 연산자를 이스케이프하기 위한 매개변수 확장 플래그가 있으며 해당 쉘에서 N
ullglob 한정자와 함께 globbing을 사용하여 블랙리스트를 실제로 존재하는 디렉토리로 줄이고 매개변수를 P
다시 연결할 수 있습니다 .-o
-path
() {
find . '(' $@[2,-1] ')' -prune -o -type f -print
} ./${(f)^"$(<.blacklist.txt)"}(Ne['REPLY=${(b)REPLY%/}']P[-o]P[-path])
여기서는 glob 한정자를 사용하여 합계를 각 파일 앞에 추가 P
하고 목록을 익명 함수에 전달하며 마지막에서 두 번째 인수를 ( )에 전달합니다. 결과에서 자신을 생략 하고 싶다면 다음과 같이 할 수도 있습니다.-o
-path
P
find
$@[2,-1]
.blacklist.txt
find . '(' -path ./.blacklist.txt ./${(f)^"$(<.blacklist.txt)"}(Ne['REPLY=${(b)REPLY%/}']P[-o]P[-path]) ')' -prune -o -type f -print
GNU 또는 호환 버전이 있는 경우 이스케이프 , 후행 제거 또는 접두어 제거 가 필요하지 않은 find
대안은 다음을 사용하는 것입니다 .-path
/
./
-samefile
find . '(' -samefile .blacklist.txt ${(f)^"$(<.blacklist.txt)"}(NP[-o]P[-path]) ')' -prune -o -type f -print
답변3
perl
나는 이것을 하곤 했을 것이다파일::찾기find
많은 조건자를 사용하여 긴 명령을 작성하는 것은 단순한 절차적 스크립트를 작성하는 것에 비해 PITA이기 때문입니다.
#!/usr/bin/perl
use strict;
use File::Find;
use autodie qw(open);
# load the blacklist into an array
my @blacklist;
open(my $BL,"<","blacklist.txt");
while(<$BL>) {
# Assume one directory per line. Paths are treated
# as regular expressions, not as literal strings.
chomp;
push @blacklist, $_;
};
close($BL);
# generate a regexp to match all the entries in the array
our $blacklist_re = "^(?:" . join("|",@blacklist) . ")";
#print "$blacklist_re\n";
find { wanted => \&wanted, preprocess => \&prune }, '.';
sub wanted { -f && print "$File::Find::name\n" };
sub prune { return grep { ! -d || ! m/$blacklist_re/ } @_ };
2023년 4월 24일 업데이트 - 이 버전은 사전 처리 서브루틴을 사용하여 원치 않는 디렉터리가 File::Find
해당 디렉터리에 포함되지 않도록 실제로 정리합니다.
prune
전처리 서브루틴은 디렉토리가 입력될 때 마다 호출됩니다 find
(이 경우 find
함수에 인수로 제공된 모든 디렉토리 포함 .
).
디렉터리(디렉터리, 소켓, 장치 노드 등 포함)의 파일 이름은 배열로 전달되며 prune
디렉터리가 아니거나 블랙리스트 정규식과 일치하지 않는 파일 이름 배열을 반환합니다. 이것은 Perl의 내장 grep
기능을 사용합니다. 이것은 외부 명령이 아니며 /bin/grep
파일이 아닌 목록/배열에서 작동합니다. 바라보다 perldoc -f grep
.
정리에 의해 반환된 배열에 없는 파일 이름은 추가 처리에서 제외되므로 find
서브 wanted
루틴은 단순한 테스트로 축소되었습니다 -f
. wanted
정리된 디렉터리나 파일은 전혀 표시되지 않습니다.
실행 예시:
다음을 포함하는 blacklist.txt
파일 :
foo bar
bar baz
다음 디렉토리와 일부 더미 파일을 만듭니다.
mkdir 'foo bar' 'bar baz'
touch 'bar baz/a.txt' 'foo bar/b.txt' 'foo bar/c.txt'
touch d.txt
디렉토리 구조는 다음과 같습니다.
.
├── bar baz
│ └── a.txt
├── blacklist.txt
├── d.txt
├── ff.pl
├── foo bar
├── b.txt
└── c.txt
스크립트를 실행하면(예를 들어 다른 이름으로 저장 ff.pl
하고 실행 가능하게 설정 chmod +x ff.pl
) 다음과 같은 출력이 생성됩니다.
$ ./ff.pl
./ff.pl
./blacklist.txt
./d.txt
즉, blacklist.txt의 디렉터리는 출력에서 제외됩니다.
답변4
내가 할 수 있는 일(가정 bash
및 GNU find
):
find . -exec fgrep -qx \{} $(sed 's#/$##' .blacklist.txt) \; -prune -o -type f -print
블랙리스트가 길면 그다지 효율적이지 않지만 대부분의 파일 이름에는 작동합니다. (최대: 예를 들어 줄 바꿈이 포함된 파일 이름은 여전히 문제입니다. )