Como encontrar o último campo usando 'cut'

310

Sem usar sedou awk, apenas cut , como obtenho o último campo quando o número de campos é desconhecido ou muda a cada linha?

noobcoder
fonte
8
Você está apaixonado pelo cutcomando :)? porque não outros comandos do Linux?
Jayesh Bhoi
7
Sem sedou awk: perl -pe 's/^.+\s+([^\s]+)$/$1/'.
jordanm
3
possível duplicata de como dividir uma string em shell e obter o último campo
Amir Ali Akbari
4
@MestreLion Muitas vezes as pessoas leem uma pergunta para encontrar uma solução para uma variação de um problema. Este começa com a premissa falsa que cutsuporta algo que não suporta. Mas achei útil, pois força o leitor a considerar um código mais fácil de seguir. Eu queria um rápido, muito simples de usar cut, sem necessidade de utilizar várias sintaxes para awk, grep, sed, etc. A revcoisa fez o truque; muito elegante e algo que eu nunca considerei (mesmo que desajeitado para outras situações). Também gostei de ler as outras abordagens das outras respostas.
Beejor 23/08/16
3
Vim aqui um problema da vida real: quero encontrar todas as extensões de arquivo diferentes em uma árvore de origem, para atualizar um arquivo .gitattributes. Assim find | cut -d. -f<last>é a inclinação natural
studog

Respostas:

680

Você pode tentar algo como isto:

echo 'maps.google.com' | rev | cut -d'.' -f 1 | rev

Explicação

  • rev reverte "maps.google.com" para ser moc.elgoog.spam
  • cut usa ponto (ou seja, '.') como delimitador e escolhe o primeiro campo, que é moc
  • por fim, revertemos novamente para obter com
zedfoxus
fonte
6
Não está usando apenas cutmas está sem sedou awk.Então, o que OP pensa?
Jayesh Bhoi
7
A @tom OP fez mais perguntas do que apenas isso nas últimas horas. Com base em nossas interações com o OP, sabemos que awk / sed / etc. não são permitidos em sua lição de casa, mas uma referência a rev não foi feita. Então valeu a pena
tentar
4
@ zfus eu vejo. Pode querer ficar com outro revdepois.
tom
17
revideal duplo grande!
Ford Guo
6
Impressionantes, simples, perfeito, obrigado pela explicação também - não basta as pessoas explicando cada passo em longas cadeias de comandos canalizada
Pete
128

Use uma expansão de parâmetro. Isso é muito mais eficiente do que qualquer tipo de comando externo, cut(ou grep) incluído.

data=foo,bar,baz,qux
last=${data##*,}

Consulte o BashFAQ # 100 para obter uma introdução à manipulação nativa de strings no bash.

Charles Duffy
fonte
3
@ ErwinWessels: Porque o bash é realmente lento. Use o bash para executar pipelines, não para processar dados em massa. Quero dizer, isso é ótimo se você já tem uma linha de texto em uma variável de shell ou se deseja while IFS= read -ra array_var; do :;done <(cmd)processar algumas linhas. Mas para um arquivo grande, rev | cut | rev é provavelmente mais rápido! (E, claro awk será mais rápido do que isso.)
Peter Cordes
2
@ PeterCordes, o awk será mais rápido para um arquivo grande, com certeza, mas é preciso um pouco de entrada para superar os custos de inicialização com fator constante. (Também existem shells - como o ksh93 - com desempenho mais próximo do awk, onde a sintaxe fornecida nesta resposta permanece válida; o bash é excepcionalmente lento, mas não chega nem perto da única opção disponível).
Charles Duffy
1
Obrigado @PeterCordes; como sempre, acho que cada ferramenta tem seus casos de uso.
Erwin Wessels
1
Essa é de longe a maneira mais rápida e concisa de aparar uma única variável dentro de um bashscript (supondo que você já esteja usando um bashscript). Não há necessidade de chamar nada externo.
Ken afiada
1
@ Balmipour, ... no entanto, rev é específico para qualquer sistema operacional que você esteja usando que o fornece - não é padronizado em todos os sistemas UNIX. Veja a lista de capítulos da seção POSIX sobre comandos e utilitários - não está lá. E não${var##prefix_pattern} é , de fato, específico do bash; está no padrão POSIX sh , consulte o final da seção 2.6.2 (vinculada); portanto , ao contrário , está sempre disponível em qualquer shell compatível. rev
Charles Duffy
89

Não é possível usar apenas cut. Aqui está uma maneira de usar grep:

grep -o '[^,]*$'

Substitua a vírgula por outros delimitadores.

tom
fonte
3
Para fazer o oposto, e encontrar tudo, exceto o último campo fazer:grep -o '^.*,'
Ariel
2
Isso foi especialmente útil, porque revadicione um problema de caracteres unicode multibyte no meu caso.
Brice
3
Eu estava tentando fazer isso no MinGW, mas minha versão grep não suporta -o, então usei o sed 's/^.*,//'que substitui todos os caracteres até e incluindo a última vírgula por uma string vazia.
TamaMcGlinn
46

Sem awk? ... Mas é tão simples com awk:

echo 'maps.google.com' | awk -F. '{print $NF}'

AWK é uma ferramenta muito mais poderosa para ter no seu bolso. -F se para o separador de campos NF for o número de campos (também representa o índice dos últimos)

Amir Mehler
fonte
2
Isso é universal e funciona exatamente como esperado todas as vezes. Nesse cenário, usar cutpara alcançar a saída final do OP é como usar uma colher para "cortar" o bife (trocadilho intencional :)). awké a faca de bife.
Hickory420
3
Evite o uso desnecessário, echopois isso pode atrasar o script para arquivos longos usando awk -F. '{print $NF}' <<< 'maps.google.com'.
Anil_M 17/10/19
14

Existem várias maneiras. Você também pode usar isso.

echo "Your string here"| tr ' ' '\n' | tail -n1
> here

Obviamente, a entrada de espaço em branco para o comando tr deve ser substituída pelo delimitador necessário.

rjni
fonte
Obrigado! algo que funciona em busybox sh 1.0.0 :)
kevinf
1
Isto parece a resposta mais simples para mim, menos tubos e significado mais claro
joeButler
1
Isso não funcionará para um arquivo inteiro, o que provavelmente significa o OP.
Amir
7

Esta é a única solução possível para usar nada além de cortar:

eco "string" | cut -d '.' -f2- [repeat_following_part_forever_or_until_out_of_memory:] | cut -d '.' -f2-

Usando esta solução, o número de campos pode realmente ser desconhecido e variar de tempos em tempos. No entanto, como o comprimento da linha não deve exceder os caracteres ou campos LINE_MAX, incluindo o caractere de nova linha, um número arbitrário de campos nunca poderá fazer parte como uma condição real desta solução.

Sim, uma solução muito boba, mas a única que atende aos critérios que eu acho.

Um amigo
fonte
2
Agradável. Basta pegar o último '.' fora de "string" e isso funciona.
Matt
2
Adoro quando todo mundo diz que algo é impossível e então alguém fala com uma resposta funcional. Mesmo que seja realmente muito bobo.
Beejor 23/08/16
Pode-se iterar cut -f2-em um loop até que a saída não seja mais alterada.
loa_in_ 25/06/19
4

Se sua string de entrada não contém barras, você pode usar basenamee um subshell:

$ basename "$(echo 'maps.google.com' | tr '.' '/')"

Isso não usa sedou awktambém não usa cut, então não tenho certeza se ele se qualifica como resposta à pergunta conforme está redigido.

Isso não funciona bem se o processamento de seqüências de entrada que podem conter barras. Uma solução alternativa para essa situação seria substituir a barra por outro caractere que você sabe que não faz parte de uma sequência de entrada válida. Por exemplo, o |caractere pipe ( ) também não é permitido nos nomes de arquivos, portanto, isso funcionaria:

$ basename "$(echo 'maps.google.com/some/url/things' | tr '/' '|' | tr '.' '/')" | tr '|' '/'
jstine
fonte
2

o seguinte implementa a sugestão de um amigo

#!/bin/bash
rcut(){

  nu="$( echo $1 | cut -d"$DELIM" -f 2-  )"
  if [ "$nu" != "$1" ]
  then
    rcut "$nu"
  else
    echo "$nu"
  fi
}

$ export DELIM=.
$ rcut a.b.c.d
d
user2166700
fonte
2
Você precisa de aspas duplas ao redor dos argumentos echopara que isso funcione de maneira confiável e robusta. Veja stackoverflow.com/questions/10067266/…
tripleee
0

Se você tiver um arquivo denominado filelist.txt, é um caminho de lista como o seguinte: c: /dir1/dir2/file1.h c: /dir1/dir2/dir3/file2.h

então você pode fazer isso: rev filelist.txt | corte -d "/" -f1 | rev

aperson1961
fonte
0

Adicionando uma abordagem a essa pergunta antiga apenas por diversão:

$ cat input.file # file containing input that needs to be processed
a;b;c;d;e
1;2;3;4;5
no delimiter here
124;adsf;15454
foo;bar;is;null;info

$ cat tmp.sh # showing off the script to do the job
#!/bin/bash
delim=';'
while read -r line; do  
    while [[ "$line" =~ "$delim" ]]; do
        line=$(cut -d"$delim" -f 2- <<<"$line")
    done
    echo "$line"
done < input.file

$ ./tmp.sh # output of above script/processed input file
e
5
no delimiter here
15454
info

Além do bash, apenas o corte é usado. Bem, e eco, eu acho.

Kaffe Myers
fonte
Meh, por que não apenas remover o corte completamente e usar apenas o bash ... x] while read -r line; do echo ${line/*;}; done <input.fileproduz o mesmo resultado.
Kaffe Myers
-1

Percebi que se apenas garantirmos que existe um delimitador à direita, ele funciona. Portanto, no meu caso, tenho delimitadores de vírgula e espaço em branco. Eu adiciono um espaço no final;

$ ans="a, b"
$ ans+=" "; echo ${ans} | tr ',' ' ' | tr -s ' ' | cut -d' ' -f2
b
AnneTheAgile
fonte
E ans="a, b, c"produz b, que não atende aos requisitos de "o número de campos é desconhecido ou muda a cada linha" .
JWW