공백을 유지하면서 명령줄 인수 바꾸기

공백을 유지하면서 명령줄 인수 바꾸기

실행 중인 다운스트림 명령에 맞게 자동으로 형식을 지정하기 위해 전달되는 명령줄 인수를 선택적으로 바꾸고 싶습니다. 논쟁에는 공백이 있을 것이고, 그것이 논쟁의 내용입니다.

나는 현재 이 일을 하고 있습니다:

set -- $(echo $* | sed -e "s/$_ARG/--description=\"$_ID - $_SUMMARY\"/")

새 인수가 --description="$_ID - $_SUMMARY"분할됩니다.

다운스트림 명령을 실행합니다.

<cmd> "$@"

매개변수는 얼마든지 가질 수 있지만 샘플 사용 사례는 다음과 같습니다.

~에서

activity --description='handle null'

도착하다:

activity --description='$SOME_VARIABLE - handle null'

결국 다운스트림 명령을 실행하면 "$@"을 사용해도 이미 분할되어 있어 예상대로 작동하지 않습니다. 그것은 결국 다음과 같이 된다

activity --description=value - handle null

--description=value, -, handle, null는 별도의 매개변수로 처리됩니다.

답변1

ksh93, zsh 또는 bash에서는 다음을 수행할 수 있습니다.

set -- "${@/#--description=*/--description=$NEW_DESCRIPTION}"

시작 위치 매개변수( #시작 부분에 패턴을 고정하는) --description=를 로 바꾸려면 --description=<contents-of-NEW_DESCRIPTION-variable.

를 사용하면 ksh93다음과 같이 단축될 수 있습니다.

set -- "${@/#@(--description=)*/\1$NEW_DESCRIPTION}"

다음과 동일 zsh -o extendedglob:

set -- "${@/#(#b)(--description=)*/$match[1]$NEW_DESCRIPTION}"

하지만 어쩌면 이렇게 할 수도 있습니다:

set -- "$@" "--description=$NEW_DESCRIPTION"

대부분의 유틸리티는 동일한 옵션의 여러 사용을 허용하며 마지막으로 발생한 옵션이 우선 적용됩니다. 예를 들어:

$ echo x | grep -H --label=foo --label=bar .
bar:x

에서는 zsh다음을 수행할 수 있습니다.

argv[(i)--description=*]=--description=$NEW_DESCRIPTION

로 시작하는 첫 번째 인수를 바꾸 거나 --description=, --description=<contents-of-NEW_DESCRIPTION-variable찾을 수 없는 경우 새 인수로 추가합니다.

또는:

argv[(I)--description=*]=--description=$NEW_DESCRIPTION

교체되는 마지막 일치 항목이라는 점과 일치하지 않는 경우 처음에 삽입된다는 점만 제외하면 동일합니다.

하나의 매개변수를 여러 매개변수로 바꿀 수도 있습니다.

argv[(i)--description=*]=(--description=$NEW_DESCRIPTION --other-args)

--description또는 매개변수와 후속 매개변수를 다음으로 바꾸세요 --description=$NEW_DESCRIPTION.

argv[n=argv[(i)--description],n+1]=--description=$NEW_DESCRIPTION

--description(다시 말하지만, 끝에 추가된 요소에서는 if가 발견되지 않습니다.)

제거하다모두매개변수는 다음 형식으로 시작 --description=하고 끝에 하나를 추가합니다.

set -- "${@:#--description=*}" --description=$NEW_DESCRIPTION

4.4+ 에서 bash매개변수를 일부 변환하는 또 다른 옵션은 perl위치 매개변수를 인수로 전달하고 이를 NUL로 구분된 목록으로 다시 읽는 것입니다( bash변수는 어쨌든 NUL을 포함할 수 없으므로).

readarray -td '' newargs < <(
  SEARCH="$_ARG" REPLACE='--description=something' perl -l0e '
    for (@ARGV) {
      s/\Q$ENV{SEARCH}\E/$ENV{REPLACE}/;
      print;
    }' -- "$@"
)
set -- "${newargs[@]}"

sedSEARCH 및 REPLACE를 일부 이스케이프 처리하는 것보다 낫습니다.


1 예외는 누적됩니다. 예를 들어 일부 유틸리티보다 --quiet --quiet조용하거나 여러 출력 필드를 지정 --quiet하는 데 사용됩니다 . 어떤 경우에는 순서가 중요합니다. 예를 들어 로 변경 하면 로 변경하는 것과 동일한 작업이 수행되지 않을 수 있습니다 .-o pid -o ppidps--description=foo --no-description--description=bar --no-description--description=foo --no-description --description=bar

답변2

코드에 몇 가지 문제가 있습니다. 그 중 하나는 따옴표 없이 사용하는 것입니다 $*. 그러면 쉘이 원시 인수를 단어의 모든 문자 $IFS(기본적으로 공백, 탭, 줄 바꿈)로 분할하고 결과 단어에 파일 이름 글로빙을 적용합니다. 공백, 탭 또는 개행 문자가 포함된 여러 인수를 지원하려는 경우 $*단일 문자열이 되므로 인용하는 것도 원하는 것이 아닙니다. 가독성 을 위해 공백을 두고 각 인수에 대해 하나만 생성되므로 "$*"using으로 전환하는 것은 "$@"도움이 되지 않습니다 .echosed

echo백슬래시 시퀀스(예: 및 )가 포함된 문자열은 셸 및 현재 설정에 따라 \n특별하게 처리될 수 있습니다 . \t일부 셸에서는 echo -n출력이 없을 수 있습니다 -n(예: 다른 문제가 있는 문자열이 있을 수 있음 -e).

이를 텍스트로 처리하려는 경우(매개변수는 여러 줄 문자열일 수 있음) sed수정 매개변수를 사용하면 단일 매개변수에 대해 작동할 수 있지만 이 경우 모든 매개변수에 일부 편집 스크립트를 한 번에 적용하므로 잘못 실행될 수 있습니다.

그러나 결과 문자열을 분할하는 것은 명령과 함께 사용되는 따옴표가 아닌 대체입니다 set. 그러면 결과가 다시 분할 sed되고 파일 이름 글로빙이 결과에 다시 적용됩니다.

수정하려는 명령줄 옵션을 구문 분석해야 합니다. 즉, 매개변수를 반복하고 수정하려는 매개변수를 수정합니다.

다음 스크립트는 긴 옵션의 각 인스턴스에 대한 옵션 인수의 시작 부분에 sh문자열을 추가합니다 . 긴 옵션 뒤에 공백이 있으면(예:) 호출 스크립트를 사용하는 것처럼 스크립트가 최종으로 수정되기 전에 a로 다시 작성됩니다 .hello ---description--description "my thing"=--description="my thing"--description="hello - my thing"

#!/bin/sh

SOME_VARIABLE=hello

skip=false

for arg do
    if "$skip"; then
        skip=false
        continue
    fi

    # Re-write separate option-argument with "=".
    # This consumes an extra argument, so need to skip
    # next iteration of the loop.
    case $arg in
        --description)
            arg=--description=$2
            shift
            skip=true
    esac

    # Add the value "$SOME_VARIABLE - " to the start of the
    # option-argument of the --description long option.
    case $arg in
        --description=*)
            arg=--description="$SOME_VARIABLE - ${arg#--description=}"
    esac

    # Put the (possibly modified) argument back at the end
    # of the list of arguments and shift off the first item.
    set -- "$@" "$arg"
    shift
done

# Print out the list of arguments as strings within "<...>":
printf '<%s>\n' "$@"

${arg#--description=}--description=value 에서 접두사 문자열을 제거하고 $arg원래 옵션 인수 문자열은 그대로 둡니다.

실행 예시:

$ sh ./script -a -b --description="my thing" -c -d --description "your thing" -e
<-a>
<-b>
<--description=hello - my thing>
<-c>
<-d>
<--description=hello - your thing>
<-e>

항상 긴 옵션과 문자로 구분된 해당 옵션 인수가 필요하다면 =코드가 상당히 단순화될 수 있습니다.

#!/bin/sh

SOME_VARIABLE=hello

for arg do
    # Add the value "$SOME_VARIABLE - " to the start of the
    # option-argument of the --description long option.
    case $arg in
        --description=*)
            arg=--description="$SOME_VARIABLE - ${arg#--description=}"
    esac

    # Put the (possibly modified) argument back at the end
    # of the list of arguments and shift off the first item.
    set -- "$@" "$arg"
    shift
done

printf '<%s>\n' "$@"

--description위와 동일한 매개변수를 사용하여 테스트 실행합니다(두 번째 인스턴스는 패턴과 일치하지 않으므로 수정되지 않습니다 --description=*).

$ sh ./script -a -b --description="my thing" -c -d --description "your thing" -e
<-a>
<-b>
<--description=hello - my thing>
<-c>
<-d>
<--description>
<your thing>
<-e>

bash[[ ... ]]대신 쉘 패턴 일치를 사용 하고 case ... esac배열을 사용하여 루프 중에 수정될 수 있는 매개변수를 보유하는 위의 더 짧은 두 번째 스크립트의 변형입니다 .

#!/bin/bash

SOME_VARIABLE=hello

args=()
for arg do
    if [[ $arg == --description=* ]]; then
        arg=--description="$SOME_VARIABLE - ${arg#--description=}"
    fi

    args+=( "$arg" )
done

set -- "${args[@]}"

printf '<%s>\n' "$@"

답변3

명령 대체의 출력은 쉘에서 단일 문자열로 읽혀지는 바이트 스트림입니다. 단일 매개변수의 일부가 되어야 하는 공백과 매개변수를 서로 분리해야 하는 공백에 대한 정보는 없습니다.

그러나 GNU 도구 세트와 Bash가 있고 sed를 사용하여 인수를 처리하려는 경우 인수(Bash 및 대부분의 다른 셸의 변수와 마찬가지로)는 C 문자열이며 NUL 바이트를 포함할 수 없다는 사실을 사용할 수 있습니다. GNU sed는 NUL을 줄 구분 기호로 사용할 수 있으며 Bash는 NUL로 끝나는 문자열 집합을 readarray.

예를 들어 다음과 같습니다.

# test arguments
set -- activity --description='handle null'
SOME_VARIABLE="foo bar"

# prints args NUL-terminated, run through sed, read them in to the array 'new_args'
new_args=()
readarray -t -d '' new_args < <(printf "%s\0" "$@" | sed -ze "s/^--description=/&$SOME_VARIABLE - /")
# move the values from the array to the positional parameters $1, $2 ...
set -- "${new_args[@]}"

printf "%s\0" "$@"끝에 NUL이 있는 위치 인수를 인쇄하면 sed가 NUL을 줄 구분 기호로 사용하고 sed 명령은 sed -zin의 내용을 $SOME_VARIABLE끝에 추가합니다.=--description=...

이는 $SOME_VARIABLEsed 명령에 포함되어 있으므로 일반적으로 sed에 특별한 항목(예: /&\개행 문자)이 명령을 중단시킨다는 점에 유의하는 것이 중요합니다. 또한 printf위치 매개변수가 없더라도 적어도 하나의 요소가 인쇄되므로 이것이 문제가 되는 경우 전체 요소를 if [[ "$#" -gt 0 ]]; then ....

관련 정보