Substitua uma substring por outra string no shell script

823

Eu tenho "Eu amo Suzi e me case" e quero mudar "Suzi" para "Sara".

#!/bin/bash
firstString="I love Suzi and Marry"
secondString="Sara"
# do something...

O resultado deve ser assim:

firstString="I love Sara and Marry"
Zincode
fonte
18
Eu começaria decepcionando Suzi. :)
codesniffer 25/03
Não é você, sou eu ! que deveria ter sido a corda para falar com Suzi
GoodSp33d

Respostas:

1360

Para substituir a primeira ocorrência de um padrão por uma determinada sequência, use :${parameter/pattern/string}

#!/bin/bash
firstString="I love Suzi and Marry"
secondString="Sara"
echo "${firstString/Suzi/$secondString}"    
# prints 'I love Sara and Marry'

Para substituir todas as ocorrências, use :${parameter//pattern/string}

message='The secret code is 12345'
echo "${message//[0-9]/X}"           
# prints 'The secret code is XXXXX'

(Isso está documentado no Manual de Referência do Bash , §3.5.3 "Expansão dos parâmetros do shell" .)

Observe que esse recurso não é especificado pelo POSIX - é uma extensão do Bash -, portanto, nem todos os shells do Unix o implementam. Para obter a documentação relevante do POSIX, consulte Especificações Técnicas Básicas do Open Group, Edição 7 , volume Shell & Utilities , §2.6.2 "Expansão de parâmetros" .

ruakh
fonte
3
@ruakh como escrevo esta declaração com uma condição ou. Só se eu quiser substituir Suzi ou Marry por uma nova string.
precisa saber é o seguinte
3
@ Priyatham51: Não há recurso embutido para isso. Apenas substitua um e depois o outro.
Ruakh
4
@ Bu: Não, porque \nnesse contexto se representaria, não uma nova linha. No momento, não tenho o Bash à mão para testar, mas você deve escrever algo como $STRING="${STRING/$'\n'/<br />}",. (Embora você provavelmente vai querer STRING//- substituí-all - em vez de apenas STRING/.)
ruach
25
Para deixar claro, como isso me confundiu um pouco, a primeira parte deve ser uma referência variável. Você não pode fazer echo ${my string foo/foo/bar}. Você precisariainput="my string foo"; echo ${input/foo/bar}
Henrik N
2
@mgutt: Esta resposta está vinculada à documentação relevante. A documentação não é perfeita - por exemplo, em vez de mencionar //diretamente, ela menciona o que acontece "Se o padrão começa com ' /'" (o que nem é preciso, pois o restante dessa frase assume que o extra /não é realmente parte do o padrão) - mas não conheço nenhuma fonte melhor.
ruakh 28/11/19
208

Isso pode ser feito inteiramente com a manipulação de strings do bash:

first="I love Suzy and Mary"
second="Sara"
first=${first/Suzy/$second}

Isso substituirá apenas a primeira ocorrência; para substituir todos eles, dobre a primeira barra:

first="Suzy, Suzy, Suzy"
second="Sara"
first=${first//Suzy/$second}
# first is now "Sara, Sara, Sara"
Kevin
fonte
9
Qual é o sentido de ter uma resposta que duplica a aceita, em vez de editá-la com a opção de substituição global? E adicionar que regexps são suportados, enquanto estiver nele. E explicando o que há com "Má substituição". Você tem pontos suficientes, @ Kevin :)
Dan Dascalescu
6
Parece que os dois responderam exatamente no mesmo minuto: O
Jamie Hutber 03/11/19
76

Para o Dash, todas as postagens anteriores não estão funcionando

A shsolução compatível com POSIX é:

result=$(echo "$firstString" | sed "s/Suzi/$secondString/")
Roman Kazanovskyi
fonte
1
Eu recebi o seguinte: $ echo $ (echo $ firstString | sed 's / Suzi / $ secondString / g') Adoro $ secondString e casar
Qstnr_La
2
@Qstnr_La usar aspas duplas para substituição de variáveis:result=$(echo $firstString | sed "s/Suzi/$secondString/g")
emc
1
Mais 1 para mostrar como gerar também uma variável. Obrigado!
Chef Pharaoh
2
Corrigi as aspas simples e também adicionei as aspas ausentes ao redor do echoargumento. Funciona enganosamente sem citar com strings simples, mas quebra facilmente em qualquer string de entrada não trivial (espaçamento irregular, metacaracteres de shell, etc.).
Tripleee 17/07/2018
No sh (AWS Codebuild / Ubuntu sh), descobri que precisava de uma única barra no final, não uma dupla. Vou editar o comentário, pois os comentários acima também mostram uma única barra.
Tim
67

tente isto:

 sed "s/Suzi/$secondString/g" <<<"$firstString"
Kent
fonte
19
Você realmente não precisa de Sed para isso; O Bash suporta esse tipo de substituição nativamente.
Ruakh
12
Eu acho que isso está marcado como "bash", mas veio aqui porque precisava de algo simples para outro shell. Esta é uma boa alternativa sucinta ao que wiki.ubuntu.com/… fez parecer que eu precisaria.
Natevw 30/09/14
3
Isso funciona muito bem para ash / dash ou qualquer outro POSIX sh.
Yoshua Wuyts 21/03
2
Eu recebo o erro sed: -e expressão # 1, caractere 9: opção desconhecida para `s
Nam G VU
1
Primeira resposta que funciona com stdin, ótima para strings de pipelining.
Dinei
46

É melhor usar o bash do que sedse as strings tiverem caracteres RegExp.

echo ${first_string/Suzi/$second_string}

É portátil para Windows e funciona com pelo menos a idade do Bash 3.1.

Para mostrar que você não precisa se preocupar muito com a fuga, vamos transformar isso:

/home/name/foo/bar

Nisso:

~/foo/bar

Mas somente se /home/nameestiver no começo. Nós não precisamos sed!

Dado que o bash nos fornece variáveis ​​mágicas $PWDe $HOME, podemos:

echo "${PWD/#$HOME/\~}"

EDIT: Obrigado por Mark Haferkamp nos comentários para a nota sobre citação / escape ~. *

Observe como a variável $HOMEcontém barras, mas isso não quebrou nada.

Outras leituras: Guia Avançado de Bash-Scripting .
Se o uso sedfor obrigatório, escape de todos os caracteres .

Camilo Martin
fonte
Essa resposta me impediu de usar sedcom o pwdcomando para evitar definir uma nova variável cada vez que meu personalizado $PS1é executado. O Bash fornece uma maneira mais geral do que variáveis ​​mágicas para usar a saída de um comando para substituição de string? Quanto ao seu código, tive que escapar do ~para impedir que o Bash o expandisse para $ HOME. Além disso, o que o #comando faz?
Mark Haferkamp
1
@ MarkHaferkamp Veja isso no link "leitura adicional recomendada". Sobre "escapar do ~": observe como citei as coisas. Lembre-se de sempre citar coisas! E isso não funciona apenas para variáveis ​​mágicas: qualquer variável é capaz de substituições, obtendo o comprimento da string e muito mais, no bash. Parabéns por tentar o seu $PS1jejum: você também pode estar interessado $PROMPT_COMMANDse estiver mais à vontade em outra linguagem de programação e quiser codificar um prompt compilado.
Camilo Martin
O link "leitura adicional" explica o "#". No Bash 4.3.30, echo "${PWD/#$HOME/~}"não substitui o meu $HOMEpor ~. Substituindo ~por \~ou '~'funciona. Qualquer um desses trabalhos no Bash 4.2.53 em outra distribuição. Você pode atualizar sua postagem para citar ou escapar da ~para uma melhor compatibilidade? O que eu quis dizer com a minha pergunta "variáveis ​​mágicas" foi: Posso usar a substituição de variáveis ​​do Bash na saída, por exemplo, unamesem salvá-la como variável primeiro? Quanto ao meu pessoal $PROMPT_COMMAND, é complicado .
Mark Haferkamp
@ MarkHaferkamp Whoa, você está totalmente certo, meu mal. Atualizará a resposta agora.
Camilo Martin
1
@MarkHaferkamp Bash e suas armadilhas obscuras ...: P
Camilo Martin
19

Se amanhã você decidir que não ama Marry, ela também poderá ser substituída:

today=$(</tmp/lovers.txt)
tomorrow="${today//Suzi/Sara}"
echo "${tomorrow//Marry/Jesica}" > /tmp/lovers.txt

Deve haver 50 maneiras de deixar seu amante.

Josh Habdas
fonte
9

Desde que eu não posso adicionar um comentário. @ruaka Para tornar o exemplo mais legível, escreva-o assim

full_string="I love Suzy and Mary"
search_string="Suzy"
replace_string="Sara"
my_string=${full_string/$search_string/$replace_string}
or
my_string=${full_string/Suzy/Sarah}
Nate Revo
fonte
Até que cheguei ao seu exemplo, eu tinha entendido a ordem ao contrário. Isso ajudou a esclarecer o que está acontecendo
raghav710
5

O método puro de shell POSIX , que, diferentemente da resposta baseada em Roman Kazanovskyi ,sed não precisa de ferramentas externas, apenas as expansões de parâmetros nativas do próprio shell . Observe que nomes longos de arquivos são minimizados para que o código caiba melhor em uma linha:

f="I love Suzi and Marry"
s=Sara
t=Suzi
[ "${f%$t*}" != "$f" ] && f="${f%$t*}$s${f#*$t}"
echo "$f"

Resultado:

I love Sara and Marry

Como funciona:

  • Remova o menor padrão de sufixo. "${f%$t*}"retorna " I love" se o sufixo $t" Suzi*" estiver em $f" ". I love Suzi and Marry

  • Mas se t=Zelda, então, "${f%$t*}"não excluirá nada e retornará toda a cadeia " I love Suzi and Marry".

  • Isso é usado para testar se $testá $fcom o [ "${f%$t*}" != "$f" ]que será avaliado como true se a $fstring contiver " Suzi*" e false se não.

  • Se o teste retornar verdadeiro , construa a sequência desejada usando Remover o menor padrão de sufixo ${f%$t*} " I love" e Remover o menor padrão de prefixo ${f#*$t} " and Marry", com a segunda sequência $s" Sara" no meio.

agc
fonte
5

Eu sei que isso é antigo, mas como ninguém mencionou o uso do awk:

    firstString="I love Suzi and Marry"
    echo $firstString | awk '{gsub("Suzi","Sara"); print}'
Payam
fonte
1
Esse truque é o caminho a seguir se o que você está tentando dividir não estiver em uma variável, mas em um arquivo de texto. Obrigado, @Payam!
Felipe SS Schneider
2
echo [string] | sed "s/[original]/[target]/g"
  • "s" significa "substituto"
  • "g" significa "global, todas as ocorrências correspondentes"
Alberto Salvia Novella
fonte