명령 결과를 바꿀 때 "경로는 표현식보다 앞에 있어야 합니다."를 찾습니다.

명령 결과를 바꿀 때 "경로는 표현식보다 앞에 있어야 합니다."를 찾습니다.

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*'-pruneIFSFiles*'

출력에 표시되는 인용문은 set -xBash가 출력을 셸에 대한 입력으로 표시할 수 있도록 유효하게 만들기 때문에 존재합니다. 이스케이프된 인용문은 에서 인쇄한 인용문입니다 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를 호출해야 합니다.

  1. find
  2. .
  3. (
  4. -path
  5. ./my dir
  6. -o
  7. -path
  8. ./ my \[other\] dir\?/sub dir( [그리고 이스케이프 처리됩니다. 그렇지 않으면 s에 의해 특별히 처리 ?됩니다 . 대부분의 구현에서는 이스케이프가 필요하지 않지만 해를 끼치지는 않습니다)find-path]find
  9. )
  10. -prune# 위에서 일치하는 항목을 다듬습니다.
  11. -o# 다른 것:
  12. -type
  13. f# "일반" 유형의 파일
  14. -print

따라서 각 행마다 다음이 필요합니다.

  • 후행 제거/
  • 특수 문자 이스케이프 -path:\?*[]
  • 접두사./
  • -path한 줄 앞에 추가

그런 다음 각각 사이에 인수를 배치 -o하고 결과 행을 목록으로 수집하여 find.

readarray -t이 분할을 수행하려면 bash 쉘에서 zsh및 매개변수 확장 플래그를 사용하여 수행 할 수 있습니다 f. 주변 따옴표를 생략하고 이전처럼 분할+glob을 사용할 수 있지만 먼저 개행 문자로만 설정하고 원하지 않는 glob 부분을 비활성화 $(...)해야 합니다 (zsh에서는 수행되지 않음).$IFSset -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

zshbglob 연산자를 이스케이프하기 위한 매개변수 확장 플래그가 있으며 해당 쉘에서 Nullglob 한정자와 함께 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-pathPfind$@[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

블랙리스트가 길면 그다지 효율적이지 않지만 대부분의 파일 이름에는 작동합니다. (최대: 예를 들어 줄 바꿈이 포함된 파일 이름은 여전히 ​​문제입니다. )

관련 정보