AWK: 외부 유틸리티에 전달된 인수가 필드 분할되는 것을 방지합니다.

AWK: 외부 유틸리티에 전달된 인수가 필드 분할되는 것을 방지합니다.

AWK 스크립트 내에서 변수를 외부 유틸리티에 인수로 전달할 수 있습니다.

awk 'BEGIN {
    filename = "path_to_file_without_space"
    "file " filename | getline
    print $0
}'

그러나 변수에 공백이 포함되어 있으면

awk 'BEGIN {
    filename = "path to file with spaces"
    "file " filename | getline
    print $0
}'

오류가 발생했습니다.

file: cannot open `path' (No such file or directory)

쉘이 공백에서 인용되지 않은 변수를 분할하는 방법과 마찬가지로 공백에서 인수를 분할하는 것이 좋습니다. 다음과 같이 쉘의 IFS를 null로 설정하여 쉘 필드 분할을 비활성화하고 싶습니다.

"IFS= file " filename | getline

또는 AWK 명령을 실행하기 전에 IFS를 null로 설정했지만 두 옵션 모두 차이가 없습니다. 이 필드 분할을 피하는 방법은 무엇입니까?

답변1

파일 이름을 인용해야 합니다.

awk 'BEGIN {
    filename = "path to file with spaces"
    "file \"" filename "\"" | getline
    print
}'

또는 댓글에서 제안한 대로 더 쉽게 읽을 수 있도록

awk 'BEGIN {
    DQ = "\042" # double quote (ASCII octal 42)
    filename = "path to file with spaces"
    "file " DQ filename DQ | getline
    print
}'

또는 이것이 awk더 큰 프로그램의 일부라고 가정하면,

BEGIN {
    SQ = "\047"
    DQ = "\042"
}

BEGIN {
    name = "filename with spaces"
    cmd = sprintf("file %s%s%s", DQ, name, DQ)

    cmd | getline
    close(cmd)

    print
}

즉, 열린 파일 핸들 저장이 완료되면 명령을 닫습니다. 별도의 블록에 편리한 "상수"를 설정합니다 BEGIN(이러한 블록은 순차적으로 실행됩니다). 별도의 변수를 사용하여 명령을 만듭니다 sprintf. (이 내용의 대부분은 분명히 awk유지 관리를 위해 읽을 수 있는 구조를 제공해야 하는 더 길거나 복잡한 프로그램을 대상으로 합니다 . dquote()문자열을 참조하는 및 함수를 작성하는 것을 상상할 수도 있습니다.)squote()

"파이프"의 왼쪽은 리터럴 문자열로 평가됩니다.

file "path to file with spaces"

기본적으로 using은 문자열인 단일 매개변수를 사용 cmd | getline하여 awk호출 합니다 . 따라서 실행을 사용하려면 문자열을 올바르게 인용해야 합니다 .sh -ccmdsh -c

기술적인 세부 사항은 다음을 참조하세요.POSIX 표준:

expression | getline [var]

명령 출력이 파이프되는 스트림에서 입력 레코드를 읽습니다. 스트림이 현재 열려 있지 않으면 expression명령 이름으로 값을 사용하여 생성해야 합니다. 생성된 스트림은 popen()표현식의 값을 명령 인수로, 값을 r인수 로 사용하여 함수를 호출하여 생성된 스트림 과 동일해야 합니다 mode. 스트림이 열려 있는 한 expression동일한 문자열 값으로 평가되는 후속 호출은 스트림에서 후속 레코드를 읽어야 합니다. close동일한 문자열 값으로 평가되는 표현식을 사용하여 함수가 호출될 때까지 스트림은 열린 상태로 유지되어야 합니다 . 그 때 이 함수를 호출한 것처럼 스트림이 닫힙니다 pclose(). var생략 되면 설정 $0되고 NF, 그렇지 않으면 var설정되고, 해당하는 경우 숫자 문자열로 처리됩니다(awk의 표현식 참조).

popen()여기서 언급되는 함수는 C 라이브러리 popen()함수입니다. 이것은 실행을 위해 주어진 문자열을 예약합니다 sh -c.

system()공백이 포함된 파일 이름으로 명령을 실행하면 정확히 동일한 문제가 발생하지만 이 경우 system()C 라이브러리의 함수가 호출됩니다.반품호출 sh -c방법은 비슷합니다 popen()(그러나 I/O 스트림의 파이프라인은 다릅니다).

따라서 단일 인수로 호출하면 IFS설정이 도움이 되지 않습니다.sh -c

file path to file with spaces

답변2

파일 이름에 관계없이 공백은 걱정거리가 가장 적습니다. 예를 들어, $(reboot)또는 foo;reboot #whatever또는 ...이라는 foo|reboot|bar파일을 생각해 보십시오.

awk명령줄 을 해석하기 위해 호출되므로 sh임의 입력에서 명령줄을 작성할 때 명령 주입 취약점을 방지하기 위해 매개변수를 적절하게 이스케이프하는 것이 중요합니다.cmdline | getlineprint | cmdlinesystem(cmdline)

쉘에서 인용하는 것은 까다로운 일입니다. 쉘에는 다양한 인용 연산자( '...', "...", \, $'...', $"...") 가 있지만 '...'이스케이프되지 않으므로 안전하지 않을 수 있습니다.모든특히 \해당 인코딩은 일부 문자 세트의 다른 문자 인코딩에도 존재하므로 위험한 문자를 이스케이프하지 않습니다.

또한 쉘 코드에서 이전 형태의 명령 대체를 사용하지 않는 것이 중요합니다 `...`. 이는 다른 수준의 백슬래시 처리를 도입하기 때문입니다.

환경 변수에 임의의 파일 이름이 있다고 가정합니다.

#! /bin/sh -
FILE="${1?No file provided}"
export FILE

awk -v q="'" '
  function shquote(s) {
    gsub(q, "&\"&\"&", s)
    return q s q
  }
  BEGIN {
    cmdline = "file -- " shquote(ENVIRON["FILE"])
    if ((cmdline | getline) > 0)
      print "The first line of \""cmdline"\" output was \""$0"\"."
    else
      print "Could not read a line from \""cmdline"\" output."
    if (close(cmdline) != 0)
      print cmdline" failed."
  }'

위에서는 shquote()문자열을 인수로 사용하고 sh작은 따옴표(가장 안전한 따옴표 유형)로 묶어 인용하지만 문자열 자체의 작은 따옴표는 로 변경됩니다 '"'"'. 즉, end ', 그 뒤에 따옴표 ', 또 다른 다시 열린 따옴표 "..."가 이어집니다. '다른 작은따옴표로 묶인 문자열의 경우.

위에서는 다른 가능한 경고를 확인할 수 있습니다.

  • --파일 이름이 로 끝나는지 확인하려면 이 필요합니다 -.
  • 이 명령의 출력은 file특히 파일 이름 자체에 개행 문자가 포함된 경우 한 줄에 출력된다는 보장이 없습니다. 결국 개행 문자는 파일 이름의 모든 문자만큼 유효합니다. getline하나의 레코드만 읽으며 기본 레코드는 행입니다. 바라보다awk의 후루룩 모드?전체 출력을 읽는 방법에 대한 팁.
  • 출력에 줄이 전혀 포함되지 않을 수도 있습니다. 비어 있는 첫 번째 행에서 이를 보려면 의 반환 값을 확인해야 합니다 getline.
  • 명령의 종료 상태를 확인하고 필요한 경우 문제를 보고하는 것이 좋습니다. 이는 반환된 값을 확인하여 수행됩니다 close(). 그러나 awk이 값이 종료 상태를 인코딩하는 방법에 따라 구현이 다릅니다. 유일한 공통점은 명령이 성공하면(0 종료 코드로 종료) 값이 0이라는 것입니다.

관련 정보