Bash에서: 변수 대체 시 후행 공백 캡처

Bash에서: 변수 대체 시 후행 공백 캡처

변수 값에서 여러 개의 후행 공백을 제거하려고 할 때 BASH 4.3.48(SLES12 SP4) 및 BASH 4.4.23(OpenSUSE Leap 15.1)에서 다음과 같은 현상이 나타납니다.

~> xxx="-O -Wall  "
~> echo "X${xxx%% }X"    # (1)
X-O -Wall X
~> echo "X${xxx%% *}X"
X-OX
~> echo "X${xxx% }X"
X-O -Wall X
~> echo "X${xxx% *}X"    # (2)
X-O -Wall X
~> echo "X${xxx%% \*}X"
X-O -Wall  X

나는 그 일을 하거나 해야 한다고 생각 (1)합니다 (2).

설명서에는 다음 내용이 나와 있습니다 ${parameter%%word}.

일치하는 접미사 패턴을 제거합니다. 경로 이름 확장과 마찬가지로 단어가 확장되어 패턴을 생성합니다. 패턴이 확장된 매개변수 값의 후행 부분과 일치하는 경우 확장 결과는 가장 짧은 일치 패턴(``%'' 경우) 또는 가장 긴 일치 패턴(``% %'' 사례)이 삭제되었습니다.

문서화된 대로(또는 문서를 이해한 대로) 작동하지 않기 때문에 이것이 버그인 것으로 의심됩니다( BASH에서 -Wall" "의 경우 일치하지 않는 접미사(" ")가 제거됨 ). %% *내가 맞나요?

답변1

에서 echo "X${xxx%% }X"패턴은 단일 공백입니다: . 이에 대한 가장 긴 일치 부분은 바로 단일 공백입니다. 가장 짧은 일치 부분도 바로 단일 공백입니다.

그 이상을 위해서는 globbing 연산자가 필요합니다 *. 하지만 이는 무엇이든 일치하며 -Wall. Bash globbing은 정규 표현식과 동등한 것을 직접적으로 지원하지 않습니다 a*. 당신은 필요합니다확장된 글로빙:

$ shopt -s extglob
$ echo "X${xxx%%+( )}X"
X-O -WallX

답변2

접미사 제거 내에서 접두사 제거를 사용합니다.

$ xxx="-O -Wall  "
$ echo "X${xxx%"${xxx##*[! ]}"}X"
X-O -WallX
  • 공백이 아닌 마지막 문자까지 모두 제거하고 후행 공백만 남깁니다.
  • 해당 공백을 접미사 제거 패턴으로 사용
  • 내부 매개변수 확장은 패턴으로 해석되는 것을 방지하기 위해 인용되어야 합니다(위에서는 필요하지 않지만 다른 경우에는 유용할 수 있음).
$ bash -c 'xxx="-O -Wall*   "; echo "X${xxx%%"${xxx##*[! *]}"}X"'
X-O -WallX
$ bash -c 'xxx="-O -Wall*   "; echo "X${xxx%%${xxx##*[! *]}}X"'
XX

인위적인 예이지만 내부 확장을 인용하지 않으면 포함된 별표는 외부 확장에 의해 쉘 패턴으로 처리됩니다. 인용하면 문자 그대로 별표가 됩니다.


관찰한 동작은 버그가 아니며 단순한 쉘 패턴이 작동하는 방식입니다.

${xxx%% }
  • 하나의 공간은 하나의 공간이다
  • 단일 공간의 가장 긴 발생은 단일 공간입니다.
${xxx%% *}
  • 단일 공백의 가장 긴 발생 다음에 아무 것도/아무것도 없음
  • 무엇이든/아무것도 포함되지 않습니다-Wall
${xxx% }
  • 단일 공간의 가장 짧은 발생은 단일 공간입니다
${xxx% *}
  • 단일 공백이 가장 짧게 나타나고 그 뒤에 아무것도 없음/아무 것도 단일 공백이 아닙니다.
${xxx%% \*}
  • \*백슬래시로 이스케이프 처리된 별표이며 문자 그대로 별표로 해석됩니다.
  • 변수에 공백 뒤에 별표가 붙지 않고 접미사가 제거되지 않습니다.

답변3

read또한 작동할 수 있습니다( IFS"공백"이 포함되어 있다고 가정).

xxx="-O -Wall  "
read -r xxx <<EOF
$xxx
EOF
echo "X${xxx}X"

산출:

X-O -WallX

  • read다음에 따라 입력을 필드로 분할합니다.IFS
  • IFS기본적으로 공백/탭/줄바꿈이므로 선행 및 후행 공백이 제거됩니다.
  • 변수의 첫 번째 줄에서 작동합니다(여러 줄의 변수에는 적합하지 않을 수 있으며 bash사용할 수 있음 read -d '').

답변4

간단한 매개변수 확장은 일치하고 제거할 수 있는 패턴이 상당히 제한됩니다. 문자열의 끝에서 여러 (반복되는) 문자를 제거하려면 일반적인 해결책은 실제로 먼저 문자열의 모든 문자를 제거하는 것입니다.~ 아니다문제의 문자 ${xxx##*[! ]}(모든 후행 공백). 그런 다음 두 번째 단계로 해당 확장의 결과인 모든 항목(후행 공백 모두)을 끝에서 제거하면 원하는 결과(후행 공백 제거)가 제공됩니다.

$ xxx="-O -Wall  "
$ echo "<${xxx%"${xxx##*[! ]}"}>"
<-O -Wall>

대안으로 bash에서는 확장된 글로빙을 사용할 수 있습니다.

$ shopt -s extglob
$ echo "<${xxx%%+( )}>"
<-O -Wall>

또는 더 높은 수준의 대안으로 원하는 것을 정규식과 일치시킬 수도 있습니다.

$ regex='(.*[^ ]) +$';
$ [[ $xxx =~ $regex ]] && echo "<${BASH_REMATCH[1]}>" || echo "<$xxx>"
<-O -Wall>

또는 스크립트로:

#!/bin/bash

xxx=${1:-"-O -Wall  "}

regex='(.*[^ ]) +$'

if    [[ $xxx =~ $regex ]]          # if there are trailing spaces
then 
      echo "<${BASH_REMATCH[1]}>"   # Print the string without spaces
else
      echo "<$xxx>"                 # if there are no trailing spaces.
fi

관련 정보