Um comando para imprimir apenas os últimos 3 caracteres de uma sequência


30

Eu sei que o cutcomando pode imprimir os primeiros ncaracteres de uma string, mas como selecionar os últimos ncaracteres?

Se eu tiver uma sequência com um número variável de caracteres, como posso imprimir apenas os últimos três caracteres da sequência. por exemplo.

saída "ilimitada" necessária é "ted"
A saída "987654" necessária é "654"
A saída "123456789" necessária é "789"

Respostas:


52

Por que ninguém deu a resposta óbvia?

sed 's/.*\(...\)/\1/'

... ou o menos menos óbvio

grep -o '...$'

É certo que o segundo tem a desvantagem de que linhas com menos de três caracteres desaparecem; mas a pergunta não definiu explicitamente o comportamento para este caso.


6
ougrep -o '.\{3\}$'
Avinash Raj

3
ouecho "unlimited" | python -c "print raw_input()[-3:]"
Kiro 22/10

8
@Kiro ou "echo unlimited" | java -jar EnterpriseWordTrimmer.jar, mas não acho que seja realmente necessário trazer uma linguagem mais pesada para a manipulação de personagens.
Whargin #

11
@WChargin você esqueceujava -server -Xms300M -Xmx3G -XX:+UseParallelGC -cp /path/to/all/the/jars/ -Dinput.interactive=false -Dinput.pipe=true -Dconfig.file=/path/to/config/last-three-letters.cfg -jar ...
hjk

6
grep -o -P '.{0,3}$'imprimirá os últimos 3 caracteres, mesmo que a linha tenha menos de 3 caracteres. -Pevita ter que escapar do aparelho.
Raghu Dodda

43

Mantendo-o simples - cauda

Não devemos precisar de uma expressão regular, ou mais de um processo, apenas para contar caracteres.
O comando tail, geralmente usado para mostrar as últimas linhas de um arquivo, possui uma opção -c( --bytes), que parece ser a ferramenta certa para isso:

$ printf 123456789 | tail -c 3
789

(Quando você está em um shell, faz sentido usar um método como na resposta do mikeserv, porque economiza o início do processo tail.)

Caracteres Unicode reais?

Agora, você pede os três últimos caracteres ; Não é isso que esta resposta lhe dá: ela gera os últimos três bytes !

Contanto que cada caractere tenha um byte, tail -capenas funciona. Então, ele pode ser usado se o conjunto de caracteres é ASCII, ISO 8859-1ou uma variante.

Se você tiver entrada Unicode, como no UTF-8formato comum , o resultado está errado:

$ printf 123αβγ | tail -c 3
�γ

Neste exemplo, usando UTF-8, os caracteres gregos alfa, beta e gama têm dois bytes de comprimento:

$ printf 123αβγ | wc -c  
9

A opção -mpode pelo menos contar os caracteres unicode reais:

printf 123αβγ | wc -m
6

Ok, então os últimos 6 bytes nos fornecerão os últimos 3 caracteres:

$ printf 123αβγ | tail -c 6
αβγ

Portanto, tailele não suporta manipulação de caracteres gerais e nem tenta (veja abaixo): Ele lida com linhas de tamanho variável, mas sem caracteres de tamanho variável.

Vamos colocar desta maneira: tailé ideal para a estrutura do problema resolver, mas errado para o tipo de dados.

GNU coreutils

Olhando mais, verifica-se que te coreutils GNU, a coleção de ferramentas básicas, como sed, ls, taile cut, ainda não está totalmente internacionalizado. O que é principalmente sobre o suporte ao Unicode.
Por exemplo, cutseria um bom candidato para usar em vez de cauda aqui para suporte ao personagem; Possui opções para trabalhar em bytes ou caracteres, -c( --bytes) e -m( --chars);

Somente isso -m/ --charsna versão
cut (GNU coreutils) 8.212013
não foi implementado!

De info cut:

`-c CHARACTER-LIST'
`--characters=CHARACTER-LIST'
     Select for printing only the characters in positions listed in CHARACTER-LIST.  
     The same as `-b' for now, but internationalization will change that.


Veja também esta resposta para Não é possível usar `cut -c` (` --characters`) com UTF-8? .


2
Na verdade, a maioria das outras respostas parece lidar bem com Unicode, desde que o código do idioma atual especifique a codificação UTF-8. Somente a sua cutsolução e a de Glenn Jackman não parecem.
Ilmari Karonen

@IlmariKaronen True, obrigado pela dica. Eu editei, com alguns detalhes adicionais.
Volker Siegel

11
Observe que o POSIX especifica explicitamente que taildeve lidar com bytes, e não com caracteres. Uma vez fiz um patch para adicionar uma nova opção de também selecionar caracteres, mas eu acredito que nunca chegou fundiu: - /
Martin Tournoij

Não funciona no modo de arquivo, comotail -c3 -n10 /var/log/syslog
Suncatcher

@ Scatcher eu tentei e funcionou. Qual é o problema que você vê? Seu comando tail -c3 -n10 /var/log/syslogpede as últimas 10 linhas, e isso funciona para mim. Você usa a opção -c3e, depois disso, a opção conflitante -n10. A opção posterior tem prioridade.
Volker Siegel

36

Se o seu texto estiver em uma variável de shell chamada STRING, você poderá fazer isso em a bash, zshou mkshshell:

printf '%s\n' "${STRING:(-3)}"

Ou

printf '%s\n' "${STRING: -3}"

que também tem o benefício de trabalhar com o ksh93 de onde vem essa sintaxe.

O ponto é que ele :precisa ser separado do -caso contrário, ele se torna o ${var:-default}operador do shell Bourne.

A sintaxe equivalente nos shells zshou yashé:

printf '%s\n' "${STRING[-3,-1]}"

2
Como é chamado esse tipo de sintaxe / operação para que eu possa buscar mais informações?
Tulains Córdova

6
Chama-se Expansão de Substring . É um tipo de expansão de parâmetro . O formulário geral é $ {parameter: offset: length} , mas o campo length é opcional (e, como você pode ver, foi omitido na resposta acima). DopeGhoti também poderia ter escrito ${STRING:(-3):3}(especificando o campo de comprimento ), ${STRING: -3}(com um espaço entre o :e o -), ou ${STRING: -3:3}.
G-Man Diz 'Reinstate Monica'

Nesse caso, especificar o comprimento de 3é um tanto discutível, pois está solicitando "os três caracteres do terceiro do último caractere, inclusive", que é uma operação idêntica em termos práticos a "Todos os caracteres do terceiro ao último" , inclusive ".
DopeGhoti

13

Usando awk:

awk '{ print substr( $0, length($0) - 2, length($0) ) }' file
ted
654
789

11

Se a sequência estiver em uma variável, você poderá:

printf %s\\n "${var#"${var%???}"}"

Isso retira os três últimos caracteres do valor de $varlike:

${var%???}

... e depois tira da cabeça de $vartudo, mas o que foi simplesmente despido como:

${var#"${var%???}"}

Este método tem suas vantagens e desvantagens. Pelo lado positivo, é totalmente portátil para POSIX e deve funcionar em qualquer shell moderno. Além disso, se $varnão contiver pelo menos três caracteres, nada\n será impresso, mas o ewline à direita . Então, novamente, se você quiser que seja impresso nesse caso, precisará de uma etapa adicional como:

last3=${var#"${var%???}"}
printf %s\\n "${last3:-$var}"

Dessa maneira, $last3somente estará vazio se $varcontiver 3 ou menos bytes. E $varsó é substituído por $last3se $last3está vazio ou unset- e sabemos que não é unsetporque apenas o definimos.


Isso é bem arrumado +1. Além: qualquer motivo para você não citar suas printfstrings de formato?
precisa saber é o seguinte

Por que não usar ${VARNAME:(-3)}(presumir bash)?
DopeGhoti

11
Agradeço por ter esclarecido; faz sentido, mesmo se parece (para mim) um pouco estranho ...
jasonwryan

11
@DopeGhoti - simplesmente porque essa é uma suposição que quase nunca faço. Isso funciona tão bem bashquanto em qualquer outro shell que reivindica a comapibilidade do POSIX.
mikeserv

3
@odyssey - O problema nãocsh está entre as conchas modernas compatíveis com POSIX que mencionei aqui, infelizmente. A especificação do POSIX-shell é modelada após , que se modelou após uma combinação de ambos e os shells tradicionais do estilo Bourne. incorporou a excelente funcionalidade de controle de tarefas e o redirecionamento de E / S dos antigos estilos Bourne. Ele também adicionou algumas coisas - como os conceitos de manipulação de string que demonstro acima. Provavelmente, isso não funcionará em nenhum tradicional , pelo que sei, lamento dizer. kshcshkshcshcsh
mikeserv

7

Você pode fazer isso, mas isso é um pouco ... excessivo:

for s in unlimited 987654 123456789; do
    rev <<< $s | cut -c 1-3 | rev
done 
ted
654
789

3

A solução à prova de balas para cordas utf-8:

utf8_str=$'\xd0\xbf\xd1\x80\xd0\xb8\xd0\xb2\xd0\xb5\xd1\x82' # привет

last_three_chars=$(perl -CAO -e 'print substr($ARGV[0], -3)' "$utf8_str")

Ou use:

last_three_chars=$(perl -MEncode -CO -e '
  print substr(decode("UTF-8", $ARGV[0], Encode::FB_CROAK), -3)
' "$utf8_str")

para impedir o tratamento incorreto dos dados.

Exemplo:

perl -MEncode -CO -e '
  print substr(decode("UTF-8", $ARGV[0], Encode::FB_CROAK), -3)
' $'\xd0\xd2\xc9\xd7\xc5\xd4' # koi8-r привет

Produz algo como isto:

utf8 "\xD0" does not map to Unicode at /usr/lib/x86_64-linux-gnu/perl/5.20/Encode.pm line 175.

Não depende das configurações de localidade (ou seja, funciona com LC_ALL=C). Bash, sed, grep, awk, revRequerem algo como isto:LC_ALL=en_US.UTF-8

Solução comum:

  • Receber bytes
  • Detectar codificação
  • Decodificar bytes para caracteres
  • Extrair charaсters
  • Codificar caracteres em bytes

Você pode detectar a codificação com uchardet . Veja também projetos relacionados .

Você pode decodificar / codificar com Encode no Perl, codecs no Python 2.7

Exemplo :

Extraia os últimos três caracteres da string utf-16le e converta esses caracteres em utf-8

utf16_le_str=$'\xff\xfe\x3f\x04\x40\x04\x38\x04\x32\x04\x35\x04\x42\x04' # привет

chardet <<<"$utf16_le_str"  # outputs <stdin>: UTF-16LE with confidence 1.0

last_three_utf8_chars=$(perl -MEncode -e '
    my $chars = decode("utf-16le", $ARGV[0]);
    my $last_three_chars = substr($chars, -3);
    my $bytes = encode("utf-8", $last_three_chars);
    print $bytes;
  ' "$utf16_le_str"
)

Veja também: perlunitut , HOWTO Unicode em Python 2


echoé a sua fonte à prova de balas?
mikeserv

@mikeserv, decode/encodeé minha fonte à prova de balas. Limpei minha resposta.
Evgeny Vereshchagin

Isso também depende das configurações de localidade para garantir que funcione corretamente, pois um conjunto de bytes pode refletir caracteres diferentes em conjuntos de caracteres diferentes. "Funciona" LC_ALL=Cporque é uma configuração muito "burra", mas pode quebrar quando você tenta passar uma string UTF-8 para SHIFT-5 ou uma string SHIFT-5 para KOI8, etc.
Martin Tournoij

@Carpetsmoker, obrigado. Você poderia explicar o seu comentário? Suponho que perl -CAO -e 'print substr($ARGV[0], -3)'funcione bem. Aespera-se que os elementos @ARGV sejam cadeias de caracteres codificadas em UTF-8, OSTDOUT estará em UTF-8.
Evgeny Vereshchagin

parece que você contou sobre a atribuição deutf8_str
Evgeny Vereshchagin

1

Que tal usar "expr" ou "rev"?

Uma resposta semelhante à fornecida pelo @ G-Man :expr "$yourstring" : '.*\(...\)$' tem a mesma desvantagem que a solução grep.

Um truque bem conhecido é combinar "cut" com "rev": echo "$yourstring" | rev | cut -n 1-3 | rev


A revsolução se parece muito com a de glenn jackman
Jeff Schaller

Você está certo @Jeff_Schaller: Eu perdi o de um de Glenn :-(
gildux 13/11/2015

0

Obtenha o tamanho da string com:

size=${#STRING}

Em seguida, obtenha a substring do último n caractere:

echo ${STRING:size-n:size}

Por exemplo:

STRING=123456789
n=3
size=${#STRING}
echo ${STRING:size-n:size}

daria:

789

0

tail -n 1 revisions.log | awk '{substr de impressão ($ 0, 0, comprimento ($ 0) - (comprimento ($ 0) -13)))}'

Se você deseja imprimir os primeiros treze caracteres desde o início


-1

printf não funcionará se a string tiver espaços.

Abaixo do código para string com espaço

str="Welcome to Linux"
echo -n $str | tail -c 3

nux


Hum, se printfnão funcionar, você está fazendo algo muito errado.
Kusalananda

11
@ Kusalananda: Com base no comando que Saurabh mostra, eles tentaram printf $str(em vez de printf "$str"ou printf '%s' "$str"). E sim, printf $strestá muito errado. ( echo -n $strnão é muito melhor.)
G-Man diz 'Reinstate Monica'
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.