Como dividir uma string no shell e obter o último campo


293

Suponha que eu tenha a string 1:2:3:4:5e queira obter seu último campo ( 5neste caso). Como faço isso usando o Bash? Eu tentei cut, mas não sei como especificar o último campo com -f.

Respostas:


430

Você pode usar operadores de string :

$ foo=1:2:3:4:5
$ echo ${foo##*:}
5

Isso apara tudo, desde a frente até um ':', avidamente.

${foo  <-- from variable foo
  ##   <-- greedy front trim
  *    <-- matches anything
  :    <-- until the last ':'
 }

7
Enquanto isso estiver funcionando para o problema em questão, a resposta de William abaixo ( stackoverflow.com/a/3163857/520162 ) também retornará 5se a sequência for 1:2:3:4:5:(enquanto estiver usando os operadores de sequência produzirá um resultado vazio). Isso é especialmente útil ao analisar caminhos que podem conter (ou não) um /caractere final .
Eckes

9
Como você faria o oposto disso? ecoar '1: 2: 3: 4:'?
Dobz

12
E como manter a peça antes do último separador? Aparentemente, usando ${foo%:*}. #- do começo; %- do fim. #, %- menor correspondência; ##, %%- partida mais longa.
Mihai Danila

1
Se eu quiser obter o último elemento do caminho, como devo usá-lo? echo ${pwd##*/}não funciona.
Putnik

2
@Putnik que comando vê pwdcomo uma variável. Tente dir=$(pwd); echo ${dir##*/}. Funciona para mim!
precisa

344

Outra maneira é reverter antes e depois cut:

$ echo ab:cd:ef | rev | cut -d: -f1 | rev
ef

Isso torna muito fácil obter o último, mas um campo, ou qualquer intervalo de campos numerados a partir do final.


17
Essa resposta é boa porque usa 'cut', que o autor (presumivelmente) já é familiar. Além disso, eu gosto desta resposta, porque eu estou usando o 'cut' e tinha essa pergunta exata, portanto, encontrar esta discussão através de pesquisa.
Dannid

6
Alguns forragem cut-and-paste para pessoas que usam espaços como delimitadores:echo "1 2 3 4" | rev | cut -d " " -f1 | rev
funroll

2
a rev | corte -d -f1 | rev é tão inteligente! Obrigado! Ajudou-me bastante (meu caso de uso foi rev | -d '' -f 2- | rev
EdgeCaseBerg 8/13

1
Eu sempre esqueço rev, era exatamente o que eu precisava! cut -b20- | rev | cut -b10- | rev
shearn89

Acabei com esta solução, minha tentativa de cortar caminhos de arquivos com "awk -F" / "'{print $ NF}'" quebrou um pouco para mim, pois nomes de arquivos incluindo espaços em branco também foram cortados
THX

76

É difícil obter o último campo usando o cut, mas aqui estão algumas soluções em awk e perl

echo 1:2:3:4:5 | awk -F: '{print $NF}'
echo 1:2:3:4:5 | perl -F: -wane 'print $F[-1]'

5
grande vantagem desta solução sobre a resposta aceita: ela também corresponde a caminhos que contêm ou não um /caractere de acabamento : /a/b/c/de /a/b/c/d/produz o mesmo resultado ( d) ao processar pwd | awk -F/ '{print $NF}'. A resposta aceita resulta em um resultado vazio no caso de/a/b/c/d/
eckes 23/01

@eckes No caso da solução AWK, no GNU bash, versão 4.3.48 (1), não é verdade, pois importa sempre que você tem uma barra final ou não. Basta colocar o AWK /como delimitador e, se o seu caminho for, /my/path/dir/ele usará o valor após o último delimitador, que é simplesmente uma string vazia. Portanto, é melhor evitar a barra se precisar fazer uma coisa como eu.
Stamster 21/05/19

Como obter a substring ATÉ o último campo?
blackjacx

1
@blackjacx Existem algumas peculiaridades, mas algo como awk '{$NF=""; print $0}' FS=: OFS=:muitas vezes funciona bem o suficiente.
William Pursell

29

Supondo um uso bastante simples (sem escape do delimitador, por exemplo), você pode usar grep:

$ echo "1:2:3:4:5" | grep -oE "[^:]+$"
5

Divisão - encontre todos os caracteres que não sejam o delimitador ([^:]) no final da linha ($). -o imprime apenas a parte correspondente.


-E significa usar sintaxe estendida; [^ ...] significa qualquer coisa, exceto os caracteres listados; + um ou mais desses hits (terá o comprimento máximo possível para o padrão; este item é uma extensão do gnu) - por exemplo, os caracteres que separam são dois pontos.
Alexander Stohr

18

Mão única:

var1="1:2:3:4:5"
var2=${var1##*:}

Outro, usando uma matriz:

var1="1:2:3:4:5"
saveIFS=$IFS
IFS=":"
var2=($var1)
IFS=$saveIFS
var2=${var2[@]: -1}

Ainda outro com uma matriz:

var1="1:2:3:4:5"
saveIFS=$IFS
IFS=":"
var2=($var1)
IFS=$saveIFS
count=${#var2[@]}
var2=${var2[$count-1]}

Usando expressões regulares do Bash (versão> = 3.2):

var1="1:2:3:4:5"
[[ $var1 =~ :([^:]*)$ ]]
var2=${BASH_REMATCH[1]}

11
$ echo "a b c d e" | tr ' ' '\n' | tail -1
e

Simplesmente traduza o delimitador em uma nova linha e escolha a última entrada com tail -1.


Ele falhará se o último item contiver um \n, mas na maioria dos casos é a solução mais legível.
Yajo 30/07

7

Usando sed:

$ echo '1:2:3:4:5' | sed 's/.*://' # => 5

$ echo '' | sed 's/.*://' # => (empty)

$ echo ':' | sed 's/.*://' # => (empty)
$ echo ':b' | sed 's/.*://' # => b
$ echo '::c' | sed 's/.*://' # => c

$ echo 'a' | sed 's/.*://' # => a
$ echo 'a:' | sed 's/.*://' # => (empty)
$ echo 'a:b' | sed 's/.*://' # => b
$ echo 'a::c' | sed 's/.*://' # => c

4

Se seu último campo for um único caractere, você poderá fazer o seguinte:

a="1:2:3:4:5"

echo ${a: -1}
echo ${a:(-1)}

Verifique a manipulação de strings no bash .


Isso não funciona: fornece o último caractere de a, não o último campo .
gniourf_gniourf

1
É verdade que essa é a ideia, se você souber o tamanho do último campo, é bom. Se não você tem que usar outra coisa ...
Ab Irato

4

Há muitas boas respostas aqui, mas ainda quero compartilhar esta usando o nome de base :

 basename $(echo "a:b:c:d:e" | tr ':' '/')

No entanto, ele falhará se já houver algum '/' na sua string . Se slash / é o seu delimitador, é necessário (e deve) usar o nome da base.

Não é a melhor resposta, mas apenas mostra como você pode ser criativo usando os comandos do bash.


2

Usando o Bash.

$ var1="1:2:3:4:0"
$ IFS=":"
$ set -- $var1
$ eval echo  \$${#}
0

Poderia ter usado em echo ${!#}vez de eval echo \$${#}.
24517 Rafa

2
echo "a:b:c:d:e"|xargs -d : -n1|tail -1

Primeiro, use xargs e divida-o usando ":", - n1 significa que cada linha possui apenas uma parte. Em seguida, pressione a última parte.


1
for x in `echo $str | tr ";" "\n"`; do echo $x; done

2
Isso causa problemas se houver espaço em branco em qualquer um dos campos. Além disso, ele não aborda diretamente a questão de recuperar o último campo.
chepner

1

Para aqueles que se sentem confortáveis ​​com o Python, https://github.com/Russell91/pythonpy é uma boa opção para resolver este problema.

$ echo "a:b:c:d:e" | py -x 'x.split(":")[-1]'

Desde a ajuda pythonpy: -x treat each row of stdin as x.

Com essa ferramenta, é fácil escrever código python que é aplicado à entrada.


1

Pode ser que seja um pouco tarde com a resposta, embora uma solução simples seja reverter a ordem da sequência de entrada. Isso permitiria que você sempre ganhasse o último item, independentemente do comprimento.

[chris@desktop bin]$ echo 1:2:3:4:5 | rev | cut -d: -f1
5

É importante observar que, se você usar esse método e os números forem maiores que 1 dígito (ou maiores que um caractere em qualquer circunstância), será necessário executar outro comando 'rev' sobre a saída canalizada.

[chris@desktop bin]$ echo 1:2:3:4:5:8:24 | rev | cut -d: -f1
42
[chris@desktop bin]$ echo 1:2:3:4:5:8:24 | rev | cut -d: -f1 | rev
24

Espero poder ajudar, felicidades


1

Uma solução usando a leitura incorporada:

IFS=':' read -a fields <<< "1:2:3:4:5"
echo "${fields[4]}"

Ou, para torná-lo mais genérico:

echo "${fields[-1]}" # prints the last item

0

Se você gosta de python e tem uma opção para instalar um pacote, pode usar este utilitário python .

# install pythonp
pythonp -m pip install pythonp

echo "1:2:3:4:5" | pythonp "l.split(':')[-1]"
5

python pode fazer isso diretamente: echo "1:2:3:4:5" | python -c "import sys; print(list(sys.stdin)[0].split(':')[-1])"
MortenB

@MortenB Você está enganado. Todo o objetivo do pythonppacote é fazer com que você faça as mesmas coisas que python -ccom menos tipos de caracteres. Por favor, dê uma olhada no README no repositório.
bombas

0

A correspondência de regex em sedé gananciosa (sempre vai para a última ocorrência), que você pode usar para sua vantagem aqui:

$ foo=1:2:3:4:5
$ echo ${foo} | sed "s/.*://"
5
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.