В 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}:

Удалить соответствующий шаблон суффикса. Слово расширяется для создания шаблона, как и при расширении имени пути. Если шаблон соответствует завершающей части расширенного значения параметра, то результатом расширения является расширенное значение параметра с удаленным самым коротким шаблоном соответствия (регистр ``%'') или самым длинным шаблоном соответствия (регистр ``%%'').

Поскольку это не работает так, как описано в документации (или как я понимаю документацию), я подозреваю, что это ошибка (несоответствующий суффикс (" -Wall") удаляется в случае " %% *") в BASH. Я прав?

решение1

В echo "X${xxx%% }X"шаблоне это один пробел: . Самая длинная совпадающая часть для этого — это просто один пробел. Самая короткая совпадающая часть — это тоже просто один пробел.

Для чего-то большего вам понадобится оператор подстановки *. Но это будет соответствовать чему угодно, удалив -Wall. Подстановка Bash не поддерживает напрямую эквивалент регулярного выражения 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

Связанный контент