Usuń stały prefiks / sufiks z łańcucha w Bash


484

W moim bashskrypcie mam ciąg i jego prefiks / sufiks. Muszę usunąć prefiks / sufiks z oryginalnego ciągu.

Załóżmy na przykład, że mam następujące wartości:

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

Jak przejść do następującego wyniku?

result="o-wor"


14
Zachowaj ostrożność, łącząc się z tak zwanym przewodnikiem Advanced Bash Scripting Guide; zawiera mieszankę dobrych rad i okropnych.
tripleee

Odpowiedzi:


718
$ foo=${string#"$prefix"}
$ foo=${foo%"$suffix"}
$ echo "${foo}"
o-wor

40
Istnieją również ## i %%, które usuwają jak najwięcej, jeśli przedrostek $ lub sufiks $ zawierają symbole wieloznaczne.
pts

27
Czy istnieje sposób na połączenie tych dwóch w jednej linii? Próbowałem, ${${string#prefix}%suffix}ale to nie działa.
static_rtti

28
@static_rtti Nie, niestety nie można zagnieżdżać podstawiania parametrów w ten sposób. Wiem, to wstyd.
Adrian Frühwirth

87
@ AdrianFrühwirth: cały język jest wstydem, ale jest bardzo przydatny :)
static_rtti

8
Nvm, „podstawienie bash” w Google znalazło to, czego chciałem.
Tyler

89

Za pomocą sed:

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

W poleceniu sed ^znak dopasowuje tekst zaczynający się od $prefix, a znak końcowy $dopasowuje tekst kończący się na$suffix .

Adrian Frühwirth podaje kilka dobrych uwag w komentarzach poniżej, ale sedw tym celu może być bardzo przydatny. Fakt, że treść prefiksu $ i sufiksu $ są interpretowane przez sed, może być albo dobry, albo zły - o ile tylko zwrócisz uwagę, wszystko powinno być w porządku. Piękno polega na tym, że możesz zrobić coś takiego:

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

co może być tym, czego chcesz, i jest zarówno bardziej wymyślne, jak i potężniejsze niż podstawianie zmiennych bash. Jeśli pamiętasz, że z wielką mocą wiąże się wielka odpowiedzialność (jak mówi Spiderman), powinieneś być w porządku.

Szybkie wprowadzenie do sed można znaleźć na stronie stronie http://evc-cit.info/cit052/sed_tutorial.html

Uwaga dotycząca powłoki i użycia ciągów:

W podanym konkretnym przykładzie działałyby również:

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

... ale tylko dlatego, że:

  1. echo nie obchodzi, ile ciągów znajduje się na liście argumentów, oraz
  2. W prefiksie $ i sufiksie $ nie ma spacji

Zasadniczo dobrą praktyką jest cytowanie ciągu w wierszu poleceń, ponieważ nawet jeśli zawiera spacje, zostanie on przedstawiony poleceniu jako pojedynczy argument. Cytujemy przedrostek $ i sufiks $ z tego samego powodu: każde polecenie edycyjne sed będzie przekazywane jako jeden ciąg. Używamy podwójnych cudzysłowów, ponieważ pozwalają one na zmienną interpolację; gdybyśmy użyli pojedynczych cudzysłowów, komenda sed uzyskałaby dosłowność $prefixi na $suffixpewno nie tego chcieliśmy.

Zauważ też, że używam pojedynczych cudzysłowów przy ustawianiu zmiennych prefixi suffix. Z pewnością nie chcemy interpretować niczego w ciągach znaków, dlatego zacytujemy je pojedynczo, aby nie doszło do interpolacji. Ponownie, w tym przykładzie może nie być konieczne, ale bardzo dobrym nawykiem jest wchodzenie w to.


8
Niestety jest to zła rada z kilku powodów: 1) Nie cytowany, $stringpodlega podziałowi słów i dzieleniu. 2) $prefixi $suffixmoże zawierać wyrażenia, które sedbędą interpretować, np. Wyrażenia regularne lub znak używany jako separator, który złamie całe polecenie. 3) sedDwukrotne wywołanie nie jest konieczne (możesz -e 's///' -e '///'zamiast tego), a rurki można również uniknąć. Na przykład, rozważ string='./ *'i / lub prefix='./'zobacz, jak okropnie pęka z powodu 1)i 2).
Adrian Frühwirth

Zabawna uwaga: sed może przyjmować prawie wszystko jako separator. W moim przypadku, ponieważ analizowałem katalogi prefiksów ze ścieżek, nie mogłem użyć /, więc użyłem sed "s#^$prefix##zamiast tego. (Kruchość: nazwy plików nie mogą zawierać #. Ponieważ kontroluję pliki, jesteśmy bezpieczni.)
Olie,

@Olie Nazwy plików mogą zawierać dowolny znak oprócz ukośnika i znaku null, więc jeśli nie masz kontroli, nie możesz założyć, że nazwa pliku nie zawiera określonych znaków.
Adrian Frühwirth

Tak, nie wiem o czym tam myślałem. iOS może? Dunno. Nazwy plików mogą z pewnością zawierać „#”. Nie mam pojęcia, dlaczego to powiedziałem. :)
Olie,

@Olie: Jak zrozumiałem twój oryginalny komentarz, mówiłeś, że ograniczenie twojego wyboru #jako separatora sed oznacza, że ​​nie możesz obsługiwać plików zawierających ten znak.
P Daddy

17

Czy znasz długość swojego przedrostka i przyrostka? W Twoim przypadku:

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

Lub bardziej ogólnie:

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

Ale rozwiązanie Adriana Frühwirtha jest świetne ! Nie wiedziałem o tym!


14

Używam grep do usuwania prefiksów ze ścieżek (które nie są dobrze obsługiwane sed):

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

\K usuwa z meczu wszystkie postacie przed nim.


grep -Pjest niestandardowym rozszerzeniem. Więcej mocy, jeśli jest obsługiwana na twojej platformie, ale jest to wątpliwa rada, jeśli twój kod musi być w miarę przenośny.
tripleee

@tripleee Rzeczywiście. Ale myślę, że system z zainstalowanym GNU Bash ma również grep, który obsługuje PCRE.
Vladimir Petrakovich

1
Nie, na przykład MacOS ma Bash po wyjęciu z pudełka, ale nie GNU grep. Wcześniejsze wersje faktycznie miały -Popcję z BSD, grepale ją usunęły.
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

Uwagi:

Prefiks # $: dodanie # gwarantuje, że podciąg „hell” zostanie usunięty tylko wtedy, gdy zostanie znaleziony na początku. Sufiks% $: dodanie% gwarantuje, że podciąg „ld” zostanie usunięty tylko, jeśli zostanie znaleziony na końcu.

Bez nich podciągi „hell” i „ld” zostaną usunięte wszędzie, nawet w środku.


Dzięki za notatki! qq: w twoim kodzie masz również ukośnik /zaraz po ciągu, po co to jest?
DiegoSalazar

1
/ oddziela bieżący ciąg i ciąg podrzędny. sub-string tutaj jest przyrostek w zadanym pytaniu.
Vijay Vat


6

Małe i uniwersalne rozwiązanie:

expr "$string" : "$prefix\(.*\)$suffix"

1
Jeśli używasz Bash, prawdopodobnie nie powinieneś go wcale używać expr. Było to swego rodzaju wygodne narzędzie do zlewów kuchennych w czasach oryginalnej skorupy Bourne'a, ale jest już dawno minione.
tripleee

5

Za pomocą @Adrian Frühwirth odpowiedź:

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

użyj tego w ten sposób

HELLO=":hello:"
HELLO=$(strip "$HELLO" ":")
echo $HELLO # hello

0

Korzystam z grup przechwytywania w wyrażeniach regularnych:

$ 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})).)*)upewnia się, że zawartość ${suffix}zostanie wykluczona z grupy przechwytywania. Na przykład jest to ciąg równoważny z [^A-Z]*. W przeciwnym razie otrzymasz:

$ perl -pe "s/${prefix}(.*)${suffix}/\1/g" <<< $string1
o-worldhello-wor
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.