Remova um prefixo / sufixo fixo de uma string no Bash

485

No meu bashscript eu tenho uma string e seu prefixo / sufixo. Preciso remover o prefixo / sufixo da string original.

Por exemplo, digamos que tenho os seguintes valores:

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

Como chego ao seguinte resultado?

result="o-wor"
Dušan Rychnovský
fonte
5
Dê uma olhada Advanced Bash-Scripting Guide
tarrsalah
14
Seja muito cauteloso ao vincular ao chamado Guia de script avançado do Bash; contém uma mistura de bons conselhos e péssimos.
Tripleee 19/10/16

Respostas:

719
$ foo=${string#"$prefix"}
$ foo=${foo%"$suffix"}
$ echo "${foo}"
o-wor
Adrian Frühwirth
fonte
40
Também existem ## e %%, que removem o máximo possível se $ prefix ou $ suffix contenham curingas.
pts
28
Existe uma maneira de combinar os dois em uma linha? Eu tentei, ${${string#prefix}%suffix}mas não funciona.
static_rtti
28
@static_rtti Não, infelizmente você não pode aninhar uma substituição de parâmetro como esta. Eu sei, é uma pena.
Adrian Frühwirth
87
@ AdrianFrühwirth: toda a linguagem é uma vergonha, mas é tão útil :)
static_rtti
8
Nvm, "substituição do bash" no Google encontrou o que eu queria.
Tyler #
89

Usando sed:

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

Dentro do comando sed, o ^caractere corresponde ao texto que começa com $prefix, e o final $corresponde ao texto que termina com $suffix.

Adrian Frühwirth faz bons comentários nos comentários abaixo, mas sedcom esse objetivo pode ser muito útil. O fato de o conteúdo do prefixo $ e do sufixo $ ser interpretado pelo sed pode ser bom ou ruim - desde que você preste atenção, você deve estar bem. A beleza é que você pode fazer algo assim:

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

que pode ser o que você deseja e é mais sofisticado e mais poderoso que a substituição de variáveis ​​do bash. Se você se lembra que com grande poder vem uma grande responsabilidade (como o Homem-Aranha diz), você deve ficar bem.

Uma rápida introdução ao sed pode ser encontrada em http://evc-cit.info/cit052/sed_tutorial.html

Uma observação sobre o shell e seu uso de strings:

Para o exemplo específico dado, o seguinte também funcionaria:

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

... mas apenas porque:

  1. eco não se importa com quantas seqüências de caracteres estão em sua lista de argumentos e
  2. Não há espaços no prefixo $ e no sufixo $

Geralmente, é uma boa prática citar uma sequência na linha de comando porque, mesmo que contenha espaços, ela será apresentada ao comando como um único argumento. Citamos o prefixo $ e o sufixo $ pelo mesmo motivo: cada comando de edição para sed será passado como uma sequência. Usamos aspas duplas porque elas permitem interpolação variável; se tivéssemos usado aspas simples, o comando sed teria obtido um literal $prefixe $suffixque certamente não é o que queríamos.

Observe também o uso de aspas simples ao definir as variáveis prefixe suffix. Certamente, não queremos que nada nas seqüências seja interpretado, por isso as aspas simples, para que não ocorra interpolação. Novamente, pode não ser necessário neste exemplo, mas é um hábito muito bom entrar.

Chris Kolodin
fonte
8
Infelizmente, este é um mau conselho por várias razões: 1) Não citado, $stringestá sujeito a divisão de palavras e globbing. 2) $prefixe $suffixpode conter expressões que sedinterpretarão, por exemplo, expressões regulares ou o caractere usado como delimitador, que interromperá todo o comando. 3) sedNão é necessário ligar duas vezes (você pode -e 's///' -e '///') e o tubo também pode ser evitado. Por exemplo, considere string='./ *'e / ou prefix='./'veja-o quebrar horrivelmente devido a 1)e 2).
Adrian Frühwirth
Nota divertida: sed pode levar quase tudo como um delimitador. No meu caso, como eu estava analisando os prefixos-diretórios fora dos caminhos, eu não poderia usar /, então usei sed "s#^$prefix##. (Fragilidade: nomes de arquivo não pode conter #Desde que eu controlar os arquivos, estamos seguros, não..)
Olie
Os nomes de arquivo @Olie podem conter qualquer caractere, exceto o caractere barra e nulo; portanto, a menos que você esteja no controle, não poderá assumir um nome de arquivo para não conter determinados caracteres.
Adrian Frühwirth 22/02
Sim, não sei o que eu estava pensando lá. iOS talvez? Não sei. Os nomes de arquivos certamente podem conter "#". Não faço ideia por que eu disse isso. :)
Olie 23/02
@Olie: Como eu entendi o seu comentário original, você estava dizendo que a limitação de sua escolha para usar #como delimitador do sed significava que você não podia lidar com arquivos que continham esse personagem.
P papai
17

Você sabe o tamanho do seu prefixo e sufixo? No seu caso:

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

Ou mais geral:

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

Mas a solução de Adrian Frühwirth é muito legal! Eu não sabia disso!

tommy.carstensen
fonte
14

Eu uso grep para remover prefixos de caminhos (que não são bem tratados por sed):

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

\K remove da partida todos os caracteres anteriores.

Vladimir Petrakovich
fonte
grep -Pé uma extensão não padrão. Mais poder para você, se ele é suportado em sua plataforma, mas esse é um conselho duvidoso se seu código precisar ser razoavelmente portátil.
Tripleee 28/05/19
@tripleee De fato. Mas acho que um sistema com o GNU Bash instalado também possui um grep que suporta PCRE.
Vladimir Petrakovich
1
Não, o MacOS, por exemplo, tem o Bash pronto para uso, mas não o GNU grep. Versões anteriores realmente tinham a -Popção do BSD, grepmas a removeram.
Tripleee 29/05/19
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

Notas:

# $ prefix: adicionando # garante que a subcadeia "hell" seja removida somente se for encontrada no início. Sufixo% $: adicionar% garante que a subcadeia "ld" seja removida apenas se for encontrada no final.

Sem eles, os substrings "hell" e "ld" serão removidos em todos os lugares, mesmo que sejam encontrados no meio.

Vijay Vat
fonte
Obrigado pelas notas! qq: no seu exemplo de código, você também tem uma barra /logo após a string, para que serve?
DiegoSalazar 15/05/19
1
/ separa a string atual e a sub string. sub-string aqui é o sufixo da pergunta postada.
Vijay Vat
7

Usando o =~operador :

$ string="hello-world"
$ prefix="hell"
$ suffix="ld"
$ [[ "$string" =~ ^$prefix(.*)$suffix$ ]] && echo "${BASH_REMATCH[1]}"
o-wor
Martin - マ ー チ ン
fonte
6

Solução pequena e universal:

expr "$string" : "$prefix\(.*\)$suffix"
Tosi Do
fonte
1
Se você estiver usando o Bash, provavelmente não deve usá expr-lo. Era uma espécie de utilidade conveniente para pia de cozinha nos dias da concha Bourne original, mas agora está muito além da data de validade.
Tripleee 28/05/19
5

Usando a resposta @Adrian Frühwirth:

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

use assim

HELLO=":hello:"
HELLO=$(strip "$HELLO" ":")
echo $HELLO # hello
math2001
fonte
0

Eu usaria grupos de captura no regex:

$ 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})).)*)garante que o conteúdo de ${suffix}será excluído do grupo de captura. Em termos de exemplo, é a string equivalente a [^A-Z]*. Caso contrário, você receberá:

$ perl -pe "s/${prefix}(.*)${suffix}/\1/g" <<< $string1
o-worldhello-wor
Bayou
fonte