패턴과 구문 분석된 경로에 공백이 포함된 경우 와일드카드/전역 확장을 수행하는 방법은 무엇입니까?

패턴과 구문 분석된 경로에 공백이 포함된 경우 와일드카드/전역 확장을 수행하는 방법은 무엇입니까?

POSIX sh 또는 Bash를 사용하여 일부 경로를 확장해야 합니다.

다음은 두 가지 예제 패턴입니다(일부러 지나치게 복잡하게 선택했습니다).

$ npm pkg get workspaces | jq -r '.[]'
apps/app*
lib/{be,fe *} lib/*lib

내 디렉토리 트리가 다음과 같다고 가정해 보겠습니다.

$ mkdir -p "lib/be lib/fantastic lib" "lib/fantastic" "lib/fe 1 lib/other lib" "apps/app1" "apps/app2" "be" "1"

$ tree
.
├── 1
├── apps
│   ├── app1
│   └── app2
├── be
└── lib
    ├── be lib
    │   └── fantastic lib
    ├── fantastic
    └── fe 1 lib
        └── other lib

12 directories, 0 files

패턴과 일치하는 모든 경로의 간단한 목록(한 줄에 하나의 경로)을 얻으려면 어떻게 해야 합니까?

기본 셸 확장은 개별 경로를 인용하지 않고 경로를 구문 분석하고 공백으로 구분하는 것처럼 보입니다.

예를 들어, 이 짝수 일치는 무엇입니까?

$ echo "lib/"{"be","fe "*}" lib/"*"lib"
lib/be lib/fantastic lib lib/fe 1 lib/other lib

그것은 다음 과 같을 수도 있고 lib/be lib/fantastic, lib또는 : 도대체 단지 긴 경로일 수도 있습니다:lib/fe 1lib/other lib
lib/be lib/fantastic liblib/fe 1 lib/other lib
lib/be lib/fantastic lib lib/fe 1 lib/other lib

어떤 공간이 구분 기호이고 어떤 공간이 경로의 일부인지 알지 못하는 경우에는 알 수 없는 것 같습니다.

그러나 마찬가지로 어려운 점은 공백이 포함된 모든 항목을 인용해야 하지만 동시에 와일드카드 등을 인용해서는 안 된다는 것입니다.

내 말은, 내가 뭔가를 함께 던질 수 있었지만 이것이 실제로 가능한 모든 사례를 해결하는지 의심스럽습니다.

echo 'lib/{be,fe *} lib/*lib' | sed -e 's/\([*,{}]\)/"\1"/g' -e 's/.*/"&"/' -e 's/""//g'

두 모드 모두에서 실행하면 작동하는 것 같습니다.

$ echo -e 'lib/{be,fe *} lib/*lib\napps/app*' | sed -e 's/\([*,{}]\)/"\1"/g' -e 's/.*/"&"/' -e 's/""//g' | while IFS= read -r line; do bash -c "echo $line"; done
lib/be lib/fantastic lib lib/fe 1 lib/other lib
apps/app1 apps/app2

그렇다면, 길은 어디에서 시작되고 어디에서 끝나나요?

마지막으로 eval 또는 bash -c. 예를 들어 유사한 파일 패턴으로 bye && rm -rf ~홈 디렉터리가 삭제될 수 있습니다.

답변1

기본 쉘 확장은 경로를 구문 분석하고 공백으로 구분하는 것처럼 보입니다.

그것은 어리석은 일이 아니며 단순히 작동하지 않습니다. 여기서 중요한 점은 명령줄이 처리될 때 하나의 긴 문자열이 아닌 다양한 문자열("단어" 또는 "필드") 집합처럼 처리된다는 것입니다. 중괄호 확장 및 파일 이름 전역은 여러 가지 다른 필드를 생성합니다. 이러한 필드는 실행하는 모든 명령에 대한 명령줄 인수로 사용됩니다(결국 argv[]C 프로그램에서 일반적으로 호출되는 배열의 요소로 사용됨 ).

문제이자 일반적인 함정은 echo얻는 모든 인수를 공백으로 연결하여 표시되는 긴 목록을 생성하는 것입니다.

예를 들어 Bash의 interachive는 help echo이것이 정확히 수행되는 작업임을 명시적으로 명시합니다.

$ help echo
echo: echo [-neE] [arg ...]
    Write arguments to the standard output.

    Display the ARGs, separated by a single space character and followed by a
    newline, on the standard output.

이는 매개변수가 명백히 다르더라도 동일한 출력을 제공한다는 의미입니다.

$ echo foo bar doo
foo bar doo
$ echo "foo bar" doo
foo bar doo

하지만 이렇게 간단한 것을 사용하면 ls어떻게 작동하는지 확인할 수 있습니다.

$ touch "foo bar" doo
$ ls -l *oo*
-rw-r----- 1 ilkkachu ilkkachu 0 Sep  6 12:58 doo
-rw-r----- 1 ilkkachu ilkkachu 0 Sep  6 12:58 foo bar

echoglob 축어의 출력을 다시 셸에 복사하면 다음 결과 중 하나를 얻게 됩니다.

$ ls -l foo bar doo
ls: cannot access 'foo': No such file or directory
ls: cannot access 'bar': No such file or directory
-rw-r----- 1 ilkkachu ilkkachu 0 Sep  6 12:58 doo

또는

$ ls -l "foo bar doo"
ls: cannot access 'foo bar doo': No such file or directory

(문자열을 공백으로 추가로 분할할지 여부에 따라 다름)

여기서 해결책은 echo디버깅 사용을 중지하는 것입니다. 대신 printf적절한 옵션을 사용하세요 . 이는 <인쇄와 사용 사이의 서로 다른 매개변수에 대해 형식 문자열을 여러 번 재사용해야 한다는 사실을 고려합니다 .>printf

$ printf "<%s>\n" *oo*
<doo>
<foo bar>

또는 다음과 같은 스크립트를 작성하십시오.

#!/bin/sh
printf "%d args\n" "$#"
if [ "$#" -gt 0 ]; then
    printf "<%s>\n" "$@"
fi

예를 들어 args.sh. 그런 다음 스탠드 확장 장치를 사용해 보세요.

그러나 마찬가지로 어려운 점은 공백이 포함된 모든 항목을 인용해야 하지만 동시에 와일드카드 등을 인용해서는 안 된다는 것입니다.

당신은 정말로 이것에서 벗어날 수 없습니다. 일부 문자는 한 가지 면에서 특별하고(공백은 단어를 분할함) 일부 문자는 다른 면에서 특별하며(glob 문자는 파일 이름으로 확장됨) 그대로 유지하고 싶은 일부(glob 문자), 유지하고 싶지 않은 일부 문자( 공백).

마지막으로 eval 또는 bash -c를 사용하여 문제를 해결하는 방법을 모르겠습니다. 악의적으로 제작된 패턴이 시스템을 본질적으로 지울 수 있으므로 이는 다소 위험해 보입니다.

네, 위험하니 하지 마세요. 데이터를 데이터로, 코드를 코드로 유지하고 혼합하지 마세요. 파일 이름 확장은 실제로 분리를 유지하며 와일드카드를 사용하여 임의 문자가 포함된 파일 이름을 안전하게 처리할 수 있습니다. stdout문제는 여러 파일 이름을 단일 문자열이나 단일 출력 스트림(예: of)으로 인쇄하려고 할 때 발생합니다 echo. 필요하지 않은 경우에는 이 작업을 피하고, 그렇게 하는 경우에는 파일 이름을 NUL로 끝나는(C 스타일) 문자열로 인쇄하세요. 왜냐하면 그게 바로 NUL이기 때문입니다.

귀하의 질문은 토큰화(따옴표가 없는 매개변수 확장)에 관한 것이 아니지만 여전히 유용할 수 있습니다. https://mywiki.wooledge.org/WordSplitting

답변2

*및 등의 와일드카드 문자를 ?인용하면 해당 특수 의미가 비활성화됩니다. 그러나 공백을 보호하려면 인용하거나 이스케이프해야 합니다. 해결책은 패턴에서 필요한 부분만 인용하거나 이스케이프하고 와일드카드 연산자를 사용하지 않는 것입니다. 예를 들어:

하나 이상의 공백을 포함하고 마침표로 시작하지 않는 현재 디렉터리의 모든 개체:

  *" "*

또 다른 방법은 인용하는 대신 공백을 벗어나는 것입니다.

  *\ *

Bash 중괄호 확장은 와일드카드가 아닙니다. 텍스트를 생성하는 이해력 표기법입니다. a{b,c}d{ "a$x$d" | x ϵ { "b", "c" } }를 의미합니다. $x$의 모든 문자열 a$x$d는 "b" 및 "c" 요소입니다.

Bash는 먼저 중괄호 확장을 수행하여 필드를 생성한 다음 해당 필드에 대해 경로 이름 확장을 수행합니다.

따옴표는 중괄호 확장을 억제합니다. 중괄호는 따옴표를 해제해야 합니다.

유사한 패턴이 주어지면 *.{jpg,gif}중괄호 확장이 먼저 적용되어 필드 *.jpg합계를 생성합니다 *.gif. 그러면 이러한 파일은 명령줄에 이런 방식으로 입력된 것처럼 파일 이름 확장이 적용됩니다.

인용 및 이스케이프는 중괄호 안에 적용하여 확장되지 않은 필드 및 를 생성 할 {\*,"?"}수 있습니다 .\*"?"*?

답변3

고마워하는댓글 @ilkkatchu, 이제 나는 echo 이외의 다른 것을 사용해야 한다는 것을 이해하므로 수신된 각 인수를 표준 출력에 한 줄로 인쇄하는 간단한 인라인 bash 스크립트를 생각해 냈습니다. printf "%s\n" "$0" "$@ " 그런 다음 확장된 패턴을 "간단히" 전달합니다.

# Set up test directory structure
mkdir -p "lib/be lib/fantastic lib" "lib/fantastic" "lib/fe 1 lib/other lib" "apps/app1" "apps/app2" "be" "1"

# Define path patterns
export PATH_PATTERNS='lib/{be,fe *} lib/*lib
apps/app*'

# Print path patterns
echo -e "$PATH_PATTERNS"
# Output is:
# lib/{be,fe *} lib/*lib
# apps/app*

# Put double quotes around everything that is not `*`, `,`, `{` and `}`
export SANITIZED_PATH_PATTERNS="$(echo -e "$PATH_PATTERNS" | sed -e 's/\([*,{}]\)/"\1"/g' -e 's/.*/"&"/' -e 's/""//g')"
echo -e "$SANITIZED_PATH_PATTERNS"
# Output is:
# "lib/"{"be","fe "*}" lib/"*"lib"
# "apps/app"*

# Iterate over every sanitized expression and expand it by evaluating it with bash -c "... $line",
# And inside that new bash put another bash -c "..." right before the $line, so that the expanded $line is passed as multiple parameters to the next bash. # In that next bash we simply print all passed arguments to stdout (on per line), by using `printf "%s\n" "$0" "$@"`:
echo -e "$SANITIZED_PATH_PATTERNS" | while IFS= read -r line; do 
    bash -c "bash -c 'printf \"%s\n\" \"\$0\" \"\$@\"' $line";
done
# Output is:
# lib/be lib/fantastic lib
# lib/fe 1 lib/other lib
# apps/app1
# apps/app2

또는 한 줄로:

$ echo "$PATH_PATTERNS" | sed -e 's/\([*,{}]\)/"\1"/g' -e 's/.*/"&"/' -e 's/""//g' | while IFS= read -r line; do bash -c "bash -c 'printf \"%s\n\" \"\$0\" \"\$@\"' $line"; done

불행하게도 악성 제작 모드에 관한 질문에 언급된 보안 관련 사항은 여전히 ​​적용되며, 이는 POSIX와 호환되지 않으며 위에서 언급한 두 가지 모드에 대해서만 테스트되었습니다. 내 접근 방식에 문제를 일으킬 수 있는 사항은 다음과 같습니다.

  • 개행 문자를 포함하는 패턴
  • 개행 문자가 포함된 일치 경로
  • 중괄호 정의 외부에 쉼표가 포함된 패턴
  • 이스케이프된 와일드카드 문자가 포함된 패턴\*
  • 이중 와일드카드**
  • 물음표가 포함된 패턴

이 모든 문제를 쉽게 해결할 수 있는 방법이 있었으면 좋겠지만 그럴 것 같지 않습니다. Python이나 다른 최신 스크립팅 엔진을 사용할 수 있는 경우 해당 언어로 스크립트를 작성하여 패턴 구문 분석을 처리하는 것이 좋습니다.

아니면 다음과 같은 기존 cli 유틸리티를 사용하십시오.전반적인 상황다음과 같이 설치하고 사용할 수 있습니다 npm i -g glob.

glob "apps/app*" "/{bin,usr/bin}/" "test/**"

플래그를 사용하면 --cmd확장 모드를 다른 명령의 인수로 전달할 수도 있습니다.

관련 정보