Удалить фиксированный префикс / суффикс из строки в Bash

485

В моем bashсценарии у меня есть строка и ее префикс / суффикс. Мне нужно удалить префикс / суффикс из исходной строки.

Например, допустим, у меня есть следующие значения:

string="hello-world"
prefix="hell"
suffix="ld"

Как мне получить следующий результат?

result="o-wor"
Душан Рихновский
источник
5
Взгляните на Руководство по
расширенному написанию
14
Будьте очень осторожны при ссылках на так называемое Advanced Bash Scripting Guide; он содержит смесь хороших советов и ужасных.
tripleee

Ответы:

720
$ foo=${string#"$prefix"}
$ foo=${foo%"$suffix"}
$ echo "${foo}"
o-wor
Адриан Фрювирт
источник
40
Есть также ## и %%, которые удаляют как можно больше, если префикс $ или суффикс $ содержат символы подстановки.
оч
28
Есть ли способ объединить два в одной строке? Я пытался, ${${string#prefix}%suffix}но это не работает.
static_rtti
28
@static_rtti Нет, к сожалению, вы не можете вложить подстановку параметров следующим образом. Я знаю, это позор.
Адриан Фрювирт,
87
@ AdrianFrühwirth: весь язык - позор, но он так полезен :)
static_rtti
8
Nvm, "замена bash" в гугле нашел то, что хотел.
Тайлер
89

Используя sed:

$ echo "$string" | sed -e "s/^$prefix//" -e "s/$suffix$//"
o-wor

Внутри команды sed ^символ совпадает с текстом, начинающимся с $prefix, а завершающий $текст совпадает с текстом, заканчивающимся на $suffix.

Адриан Фрювирт делает несколько хороших замечаний в комментариях ниже, но sedдля этого может быть очень полезным. Тот факт, что содержимое префикса $ и суффикса $ интерпретируется с помощью sed, может быть либо хорошим, либо плохим - если вы обращаете внимание, у вас все должно быть в порядке. Прелесть в том, что вы можете сделать что-то вроде этого:

$ prefix='^.*ll'
$ suffix='ld$'
$ echo "$string" | sed -e "s/^$prefix//" -e "s/$suffix$//"
o-wor

что может быть тем, что вам нужно, и одновременно и более изощренно, и мощнее, чем подстановка переменных в bash. Если вы помните, что с большой силой приходит большая ответственность (как говорит Человек-Паук), у вас все будет хорошо.

Краткое введение в sed можно найти по адресу http://evc-cit.info/cit052/sed_tutorial.html.

Примечание относительно оболочки и ее использования строк:

Для конкретного приведенного примера также будет работать следующее:

$ echo $string | sed -e s/^$prefix// -e s/$suffix$//

... но только потому, что:

  1. echo не волнует, сколько строк в его списке аргументов, и
  2. В префиксе $ и в суффиксе $ нет пробелов

Обычно рекомендуется заключать в кавычки строку в командной строке, поскольку, даже если она содержит пробелы, она будет представлена ​​команде как один аргумент. Мы заключаем в кавычки префикс $ и суффикс $ по одной и той же причине: каждая команда редактирования для sed будет передаваться как одна строка. Мы используем двойные кавычки, потому что они допускают интерполяцию переменных; если бы мы использовали одинарные кавычки, команда sed получила бы литерал, $prefixи $suffixэто, конечно, не то, что мы хотели.

Обратите внимание также на мое использование одинарных кавычек при установке переменных prefixи suffix. Мы, конечно, не хотим, чтобы что-либо в строках интерпретировалось, поэтому мы заключаем их в одинарные кавычки, чтобы не происходила интерполяция. Опять же, в этом примере это может не понадобиться, но это очень хорошая привычка.

Крис Колодин
источник
8
К сожалению, это плохой совет по нескольким причинам: 1) без кавычек, $stringможет быть разбито и разбито на слова. 2) $prefixи $suffixможет содержать выражения, которые sedбудут интерпретироваться, например, регулярные выражения или символ, используемый в качестве разделителя, который нарушит всю команду. 3) Звонить sedдва раза не нужно (можно -e 's///' -e '///'вместо этого), а трубку можно было бы избежать. Например, рассмотрите string='./ *'и / или prefix='./'и увидите, что это ужасно сломалось из-за 1)и 2).
Адриан Фрювирт
Забавное примечание: в качестве разделителя можно использовать почти все. В моем случае, так как я разбирал префиксные каталоги вне путей, я не мог использовать /, поэтому я использовал sed "s#^$prefix##вместо этого. (Хрупкость: имена файлов не могут содержать #. Поскольку я контролирую файлы, мы в безопасности.)
Оли
@Olie Имена файлов могут содержать любой символ, кроме косой черты и нулевого символа, поэтому, если вы не контролируете файл, вы не можете предполагать, что имя файла не содержит определенных символов.
Адриан Фрювирт
Да, не знаю, о чем я там думал. iOS может быть? Не знаю. Имена файлов, безусловно, могут содержать «#». Понятия не имею, почему я это сказал. :)
Оли
@ Оли: Как я понял ваш первоначальный комментарий, вы говорили, что ограничение вашего выбора использовать в #качестве разделителя sed означает, что вы не можете обрабатывать файлы, содержащие этот символ.
P Daddy
17

Вы знаете длину своего префикса и суффикса? В твоем случае:

result=$(echo $string | cut -c5- | rev | cut -c3- | rev)

Или более общий:

result=$(echo $string | cut -c$((${#prefix}+1))- | rev | cut -c$((${#suffix}+1))- | rev)

Но решение от Адриана Фрювирта круто! Я не знал об этом!

tommy.carstensen
источник
14

Я использую grep для удаления префиксов из путей (которые плохо обрабатываются sed):

echo "$input" | grep -oP "^$prefix\K.*"

\K удаляет из матча все символы перед ним.

Владимир Петракович
источник
grep -Pэто нестандартное расширение. Больше мощности, если она поддерживается на вашей платформе, но это сомнительный совет, если ваш код должен быть достаточно переносимым.
tripleee
@tripleee Действительно. Но я думаю, что система с установленным GNU Bash также имеет grep, который поддерживает PCRE.
Владимир Петракович
1
Нет, MacOS, например, имеет Bash из коробки, но не GNU grep. Более ранние версии фактически имели -Pопцию от BSD, grepно они удалили ее.
tripleee
9
$ string="hello-world"
$ prefix="hell"
$ suffix="ld"

$ #remove "hell" from "hello-world" if "hell" is found at the beginning.
$ prefix_removed_string=${string/#$prefix}

$ #remove "ld" from "o-world" if "ld" is found at the end.
$ suffix_removed_String=${prefix_removed_string/%$suffix}
$ echo $suffix_removed_String
o-wor

Ноты:

Префикс # $: добавление # гарантирует, что подстрока "ад" будет удалена, только если она найдена в начале. Суффикс% $: добавление% гарантирует, что подстрока "ld" будет удалена, только если она найдена в конце.

Без них подстроки «ад» и «ld» будут удалены везде, даже если они находятся в середине.

Виджай Ват
источник
Спасибо за заметки! qq: в вашем примере кода у вас также есть косая черта /сразу после строки, для чего это нужно ?
ДиегоСалазар
1
/ отделяет текущую строку и подстроку. подстрока здесь - суффикс в опубликованном вопросе.
Виджай Ват
7

Используя =~оператора :

$ string="hello-world"
$ prefix="hell"
$ suffix="ld"
$ [[ "$string" =~ ^$prefix(.*)$suffix$ ]] && echo "${BASH_REMATCH[1]}"
o-wor
Мартин - マ ー チ ン
источник
6

Небольшое и универсальное решение:

expr "$string" : "$prefix\(.*\)$suffix"
Тоси До
источник
1
Если вы используете Bash, вы, вероятно, не должны использовать exprвообще. Это была своего рода удобная утилита для кухонной мойки еще во времена оригинальной раковины Bourne, но сейчас она уже давно вышла из строя.
tripleee
5

Используя ответ @Adrian Frühwirth:

function strip {
    local STRING=${1#$"$2"}
    echo ${STRING%$"$2"}
}

используйте это так

HELLO=":hello:"
HELLO=$(strip "$HELLO" ":")
echo $HELLO # hello
math2001
источник
0

Я хотел бы использовать группы захвата в регулярном выражении:

$ string="hello-world"
$ prefix="hell"
$ suffix="ld"
$ set +H # Disables history substitution, can be omitted in scripts.
$ perl -pe "s/${prefix}((?:(?!(${suffix})).)*)${suffix}/\1/" <<< $string
o-wor
$ string1=$string$string
$ perl -pe "s/${prefix}((?:(?!(${suffix})).)*)${suffix}/\1/g" <<< $string1
o-woro-wor

((?:(?!(${suffix})).)*)гарантирует, что содержимое ${suffix}будет исключено из группы захвата. С точки зрения примера, это строка, эквивалентная [^A-Z]*. В противном случае вы получите:

$ perl -pe "s/${prefix}(.*)${suffix}/\1/g" <<< $string1
o-worldhello-wor
старица
источник