Exclua o último caractere de uma string usando manipulação de string no shell script

187

Gostaria de excluir o último caractere de uma string, tentei este pequeno script:

#! /bin/sh 

t="lkj"
t=${t:-2}
echo $t

mas imprime "lkj", o que estou fazendo de errado?

user3581976
fonte

Respostas:

115

Em um shell POSIX, a sintaxe ${t:-2}significa algo diferente - ela se expande para o valor de tse tfor definido e não nulo e, caso contrário, para o valor 2. Para aparar um único caractere por expansão de parâmetro, a sintaxe que você provavelmente deseja é${t%?}

Observe que em ksh93, bashou zsh, ${t:(-2)}ou ${t: -2}(observe o espaço) são legais como uma expansão de substring, mas provavelmente não é o que você deseja, pois eles retornam o substring começando com uma posição de 2 caracteres a partir do final (ou seja, ele remove o primeiro caractere ido string ijk).

Consulte a seção Expansão de parâmetros do shell do Manual de referência do Bash para obter mais informações:

chave de aço
fonte
4
Gostaria de explicar qual é a mágica por trás de '%?' ?
precisa saber é o seguinte
8
O @afraisse ${parameter%word}remove a menor correspondência de padrão de sufixo word- consulte a seção Expansão de parâmetro deman bash
steeldriver
3
Isso funciona bem para o Bash 4.1.2: $ {t%?} Para pessoas presas no CentOS / RHEL 6.x #
Joey T
185

Com bash4.2 e acima, você pode fazer:

${var::-1}

Exemplo:

$ a=123
$ echo "${a::-1}"
12

Observe que para os mais antigos bash(por exemplo, bash 3.2.5no OS X), você deve deixar espaços entre dois pontos e depois:

${var: : -1}
cuonglm
fonte
13
Isso funciona para a bashversão 4.2-alpha e acima, muito ruim a versão à qual tenho acesso é anterior. : - /
hjk 14/07
2
@iamaziz: No changelog do bash, o comprimento negativo em ${var:offset:lenght}foi adicionado apenas em bash 4.2. Talvez o OSX adicione seu próprio patch para bash.
cuonglm
1
@cuonglm não funciona: /
iamaziz 8/16
1
Não funciona no mac.
shinzou 6/08/19
1
Macsters, olhe para a resposta de Russ
P i
67

para remover os últimos ncaracteres de uma linha que não utiliza sedOR awk:

> echo lkj | rev | cut -c (n+1)- | rev

portanto, por exemplo, você pode excluir o último caractere one characterusando este:

> echo lkj | rev | cut -c 2- | rev

> lk

da revpágina de manual:

DESCRIÇÃO
O utilitário rev copia os arquivos especificados para a saída padrão, revertendo a ordem dos caracteres em cada linha. Se nenhum arquivo for especificado, a entrada padrão será lida.

ATUALIZAR:

se você não souber o comprimento da string, tente:

$ x="lkj"
$ echo "${x%?}"
lk
Networker
fonte
62

Usando sed, deve ser tão rápido quanto

sed 's/.$//'

Seu único eco é então echo ljk | sed 's/.$//'.
Usando isso, a cadeia de caracteres de 1 linha pode ter qualquer tamanho.

1111161171159459134
fonte
10
Observe que, no caso geral, ele não exclui o último caractere da sequência , mas o último caractere de todas as linhas da sequência .
Stéphane Chazelas
44

Algumas opções, dependendo do shell:

  • POSIX: t=${t%?}
  • Bourne: t=`expr " $t" : ' \(.*\).'`
  • zsh / yash: t=${t[1,-2]}
  • bash / zsh: t=${t:0:-1}
  • ksh93 / bash / zsh / mksh: t=${t:0:${#t}-1}
  • ksh93 / bash / zsh / mksh: t=${t/%?}
  • ksh93: t=${t/~(E).$/}
  • es: @ {t=$1} ~~ $t *?

Observe que, embora todos devam remover o último caractere , você descobrirá que algumas implementações (aquelas que não suportam caracteres de vários bytes) retiram o último byte (portanto, isso provavelmente corromperá o último caractere se for multi-byte )

A exprvariante assume $tque não termina em mais de um caractere de nova linha. Ele também retornará um status de saída diferente de zero se a sequência resultante acabar sendo 0( 000ou mesmo -0com algumas implementações). Também pode fornecer resultados inesperados se a sequência contiver caracteres inválidos.

Stéphane Chazelas
fonte
Agradável e completo! Mas ... eu suponho que todos esses shells suportam o POSIX, então todos deveriam apenas usá-lo para serem os mais portáteis. Menor contagem de caracteres também!
Russ
@ Russ, t=${t%?}não é Bourne, mas você provavelmente não encontrará um shell Bourne hoje em dia. ${t%?}funciona em todos os outros embora.
Stéphane Chazelas
Nenhuma opção de casca de peixe dada! Provavelmente mais popular estes dias do que ksh93 ...
rien333
@ rien333. Eu esperaria que a interface se estabilizasse um pouco. fishé um trabalho em andamento. 2.3.0 que introduziu o stringbuilt-in não foi lançado no momento das perguntas e respostas. Com a versão em que estou testando, você precisa string replace -r '(?s).\z' '' -- $t(e eu espero que eles mudem isso, eles devem mudar as bandeiras que passam para o PCRE) ou mais complicadas. Também lida mal com caracteres de nova linha, e eu sei que eles estão planejando mudar isso também.
Stéphane Chazelas
Promovido para a resposta POSIX. confirmados trabalhando em Bash 3.2.57 (1)
Avindra Goolcharan
26

A resposta mais portátil e mais curta é quase certamente:

${t%?}

Isso funciona em bash, sh, ash, dash, busybox / ash, zsh, ksh, etc.

Ele funciona usando a expansão de parâmetros do shell da velha escola. Especificamente, %especifica para remover o menor sufixo de parâmetro tcorrespondente ao padrão glob ?(ou seja: qualquer caractere).

Consulte "Remover o menor padrão de sufixo" aqui para obter uma explicação (muito) mais detalhada e mais informações. Veja também os documentos do seu shell (por exemplo man bash:) em "expansão de parâmetros".


Como observação, se você deseja remover o primeiro caractere, use-o ${t#?}, pois #corresponde da frente da string (prefixo) em vez da parte traseira (sufixo).

Também digno de nota é que ambos %e #have %%e ##versões, que correspondem à versão mais longa do padrão fornecido, em vez da mais curta. Ambos ${t%%?}e ${t##?}fariam o mesmo que seu único operador nesse caso, no entanto (portanto, não adicione o caractere inútil). Isso ocorre porque o ?padrão fornecido corresponde apenas a um único caractere. Misture um *com alguns caracteres não curinga e as coisas ficam mais interessantes com %%e ##.

Compreender as expansões de parâmetros, ou pelo menos saber sobre sua existência e saber procurá-las, é incrivelmente útil para escrever e decifrar scripts de shell de vários tipos. As expansões de parâmetro costumam parecer vodu arcano para muitas pessoas porque ... bem ... são vodu arcano (embora muito bem documentado se você souber procurar "expansão de parâmetro"). Definitivamente, é bom ter o cinto de ferramentas quando você está preso em uma concha.

Russ
fonte
Curto e doce, e funciona em MacOS e Linux!
dbernard
18
t=lkj
echo ${t:0:${#t}-1}

Você obtém uma substring de 0 ao comprimento da string -1. Observe, no entanto, que essa subtração é específica do bash e não funciona em outros shells.

Por exemplo, dashnão é capaz de analisar nem

echo ${t:0:$(expr ${#t} - 1)}

Por exemplo, no Ubuntu, /bin/shédash

Anjo
fonte
15

Você também pode headimprimir todos os caracteres, exceto o último.

$ s='i am a string'
$ news=$(echo -n $s | head -c -1)
$ echo $news
i am a strin

Infelizmente, porém, algumas versões headdo não incluem a -opção principal . Este é o caso do headque acompanha o OS X.

greenbeansugar
fonte
5

É fácil o suficiente usando expressões regulares:

n=2
echo "lkj" | sed "s/\(.*\).\{$n\}/\1/"
unxnut
fonte
5

Alguns refinamentos. Para remover mais de um caractere, você pode adicionar vários pontos de interrogação. Por exemplo, para remover os dois últimos caracteres da variável:, $SRC_IP_MSGvocê pode usar:

SRC_IP_MSG=${SRC_IP_MSG%??}
yuliskov
fonte
4

Apenas para concluir alguns usos possíveis do bash puro:

#!/bin/bash

# Testing substring removal
STR="Exemple string with trailing whitespace "
echo "'$STR'"
echo "Removed trailing whitespace: '${STR:0:${#STR}-1}'"
echo "Removed trailing whitespace: '${STR/%\ /}'"

A primeira sintaxe pega uma substring de uma string, a sintaxe é. Para a segunda, observe o sinal, que significa 'do final da linha' e a sintaxe é
${STRING:OFFSET:LENGTH}
%
${STRING/PATTERN/SUBSTITUTION}

E aqui estão duas formas mais curtas do mencionado acima

echo "Removed trailing whitespace: '${STR::-1}'"
echo "Removed trailing whitespace: '${STR%\ }'"

Observe novamente o %sinal, que significa 'Remover (ou seja, substitua por' ') o padrão de correspondência mais curto (aqui representado pelo espaço escapado ' \ ' do final do PARÂMETRO - aqui chamado STR

CermakM
fonte
1

Como também podemos usar o php na linha de comando ou scripts de shell. Às vezes, é útil para análise cirúrgica.

php -r "echo substr('Hello', 0, -1);" 
// Output hell

Com tubulação:

echo "hello" | php -r "echo substr(trim(fgets(STDIN)), 0, -1);"
// Output hell
NVRM
fonte
-1

Em ksh:

echo ${ORACLE_SID/%?/}
Alex
fonte