bash 함수에서 "grep | grep" 명령을 문자열로 실행하는 방법은 무엇입니까?

bash 함수에서 "grep | grep" 명령을 문자열로 실행하는 방법은 무엇입니까?

bash 함수에서 한 grep 명령의 결과를 다른 grep 명령으로 파이프하는 명령을 작성하려고 합니다. 궁극적으로 내가 실행하려는 명령은 다음과 같습니다.

grep -I -r FooBar /code/internal/dev/ /code/public/dev/ | grep .c:\|.h:

내가 작성 중인 함수는 명령의 첫 번째 부분을 문자열에 저장한 다음 두 번째 부분을 추가합니다.

grep_cmd="grep -I -r $pattern $@"

if (( ${#file_types[@]} > 0 )); then
    file_types="${file_types[@]}"
    file_types=.${file_types// /':\|.'}:

    grep_cmd="$grep_cmd | grep $file_types"
fi

echo "$grep_cmd"
${grep_cmd}

출력의 첫 번째 부분 이후에 오류가 발생합니다.

grep: |: No such file or directory
grep: grep: No such file or directory
grep: .c:\|.h:: No such file or directory

마지막 줄을 에서 로 변경하면 첫 번째 부분의 출력 ${grep_cmd}만 표시되고 다른 오류가 발생합니다."$grep_cmd"

bash: grep -I -r FooBar /code/internal/dev/ /code/public/dev/ | grep .c:\|.h:: No such file or directory

모든이 답변, 마지막 줄을 $(grep_cmd). 또 다른 오류가 발생합니다.

bash: grep_cmd: command not found

이 답변권장됩니다 eval $grep_cmd. 이는 오류를 억제할 뿐만 아니라 출력도 억제합니다.

이것권장됨 eval ${grep_cmd}결과는 동일합니다(오류 및 출력 억제). 나는 bash에서 디버깅을 활성화하려고 시도했는데(사용하여 set -x) 다음과 같은 결과를 얻었습니다.

+ eval grep -I -r FooBar /code/internal/dev/ /code/public/dev/ '|' grep '.c:\|.h:'
++ grep -I -r FooBar /code/internal/dev/ /code/public/dev/
++ grep '.c:|.h:'

파이프가 이스케이프되는 것처럼 보이므로 쉘은 명령을 두 개의 명령으로 해석합니다. 파이프 문자를 올바르게 이스케이프하여 명령으로 해석하려면 어떻게 해야 합니까?

답변1

의견에서 언급했듯이, 여러분이 겪고 있는 많은 어려움은 명령을 변수에 저장한 다음 나중에 명령을 실행하려고 하기 때문에 발생합니다.

명령을 저장하려고 시도하는 대신 즉시 명령을 실행하면 훨씬 더 나은 행운을 누릴 수 있습니다.

예를 들어, 다음은 달성하려는 작업을 수행해야 합니다.

if (( ${#file_types[@]} > 0 )); then
    regex="${file_types[*]}"
    regex="\.\(${regex// /\|}\):"
    grep -I -r "$pattern" "$@" | grep "$regex"
else
    grep -I -r "$pattern" "$@"
fi

답변2

쉘 프로그래밍에 대해 기억해야 할 한 가지는 데이터에는 두 가지 유형이 있으며 이는 튜토리얼에서 명확하게 설명되지 않는 경우가 많다는 것입니다.문자열과 문자열 목록. 줄 바꿈이나 공백 구분 기호가 있는 문자열과 달리 문자열 목록은 그 자체입니다.

명심해야 할 또 다른 점은 대부분의 확장은 셸이 파일을 구문 분석할 때만 적용된다는 것입니다. 명령 실행에는 확장이 포함되지 않습니다.

일부 확장은 변수 값에서 발생합니다. $foo즉, "변수 값을 가져와 foo공백을 구분 기호로 사용하여 문자열 목록으로 분할하고 목록의 각 요소를 와일드카드 패턴으로 해석한 다음 확장합니다"를 의미합니다. 이 확장은 변수가 호출 목록의 컨텍스트에서 사용될 때만 발생합니다. 문자열을 요구한다는 맥락에서 $foo이는 "변수 값을 가져옵니다 foo"를 의미합니다. 큰따옴표는 문자열 컨텍스트를 적용하므로 권장됩니다.항상 큰따옴표 안에 변수 대체 및 명령 대체를 사용하십시오: "$foo","$(somecommand)"². (변수와 마찬가지로 보호되지 않은 명령 대체에서도 동일한 확장이 발생합니다.)

구문 분석과 실행의 차이로 인해 단순히 명령을 문자열에 넣고 실행할 수는 없다는 것입니다. 를 작성하면 ${grep_cmd}구문 분석이 아닌 분할 및 와일드카드만 발생하므로 이와 같은 문자는 |특별한 의미가 없습니다.

쉘 명령을 문자열에 꼭 넣어야 한다면 eval다음과 같이 할 수 있습니다:

eval "$grep_cmd"

큰따옴표에 유의하세요. 변수 값에는 쉘 명령이 포함되어 있으므로 정확한 문자열 값이 필요합니다. 그러나 이 접근 방식은 복잡한 경향이 있습니다. 실제로 셸의 소스 구문에 뭔가가 있어야 합니다. 예를 들어, 파일 이름이 필요한 경우 파일 이름을 올바르게 인용해야 합니다. 따라서 거기에 $patternand 만 넣을 수는 없으며 $@구문 분석 시 패턴이 포함된 단일 단어와 매개변수가 포함된 단어 목록을 생성하는 문자열을 작성해야 합니다.

결론적으로:쉘 명령을 변수에 넣지 마십시오. 대신에,기능 사용. 파이프와 같은 복잡한 명령 대신 매개 변수가 있는 간단한 명령이 필요한 경우 배열을 사용할 수 있습니다(배열 변수는 문자열 목록을 저장함).

이는 가능한 접근 방식 중 하나입니다. run_grep여러분이 보여주는 코드에는 실제로 이 함수가 필요하지 않습니다. 더 큰 스크립트의 작은 부분이고 그 사이에 더 많은 코드가 있다고 가정하여 여기에 포함했습니다. 이것이 실제로 전체 스크립트라면 파이프할 대상 위치에서 grep을 실행하십시오. 또한 필터를 작성하기 위한 코드를 수정했는데, 이는 올바르지 않은 것 같습니다(예: .정규식에서 "모든 문자"는 "모든 문자"를 의미하지만 리터럴 포인트가 필요한 것 같습니다).

grep_cmd=(grep -I -r "$pattern" "$@")

if (( ${#file_types[@]} > 0 )); then
    regexp='\.\('
    for file_type in "${file_types[@]}"; do
      regexp="$regexp$file_type\\|"
    done
    regexp="${regexp%?}):"
    run_grep () {
      "${grep_cmd[@]}" | grep "$file_types"
    }
else
  run_grep () {
    "${grep_cmd[@]}"
  }
fi

run_grep

1 보다 일반적으로는 의 값을 사용합니다 IFS.
² 전문가 전용: 큰따옴표를 사용하지 않는 것이 올바른 효과를 갖는 이유를 이해하지 않는 한 변수 및 명령 대체에는 항상 큰따옴표를 사용하십시오.
전문가 전용 : 셸 명령을 변수에 입력해야 하는 경우 인용에 주의하세요.


당신이 하고 있는 일은 지나치게 복잡하고 신뢰할 수 없는 것처럼 보입니다. 포함 파일이 있다면 어떨까요 foo.c: 42? GNU grep에는 --include재귀 순회에서 특정 파일만 찾는 옵션이 있습니다. 그냥 사용하세요.

grep_cmd=(grep -I -r)
for file_type in "${file_types[@]}"; do
  grep_cmd+=(--include "*.$file_type")
done
"${grep_cmd[@]}" "$pattern" "$@"

답변3

command="grep $regex1 filelist | grep $regex2"
echo $command | bash

관련 정보