Como usar um comando shell para mostrar apenas a primeira e a última coluna em um arquivo de texto?

30

Preciso de ajuda para descobrir como usar o comando sed para mostrar apenas a primeira e a última coluna em um arquivo de texto. Aqui está o que tenho até agora para a coluna 1:

cat logfile | sed 's/\|/ /'|awk '{print $1}'

Minha fraca tentativa de mostrar a última coluna também foi:

cat logfile | sed 's/\|/ /'|awk '{print $1}{print $8}'

No entanto, isso pega a primeira e a última coluna e as mescla em uma lista. Existe uma maneira de imprimir a primeira e a última colunas claramente com os comandos sed e awk?

Entrada de amostra:

foo|dog|cat|mouse|lion|ox|tiger|bar
user70573
fonte
5
Forneça alguma amostra de entrada.
jasonwryan

Respostas:

51

Quase lá. Basta colocar as duas referências de coluna próximas uma da outra.

cat logfile | sed 's/|/ /' | awk '{print $1, $8}'

Observe também que você não precisa cataqui.

sed 's/|/ /' logfile | awk '{print $1, $8}'

Observe também que você pode dizer awkque os separadores de coluna são |, em vez de espaços em branco, para que você também não precise sed.

awk -F '|' '{print $1, $8}' logfile

Conforme as sugestões de Caleb , se você deseja uma solução que ainda gera o último campo, mesmo se não houver exatamente oito, pode usar $NF.

awk -F '|' '{print $1, $NF}' logfile

Além disso, se você deseja que a saída retenha os |separadores, em vez de usar um espaço, é possível especificar os separadores do campo de saída. Infelizmente, é um pouco mais desajeitado do que apenas usar a -Fbandeira, mas aqui estão três abordagens.

  • Você pode atribuir os separadores de campo de entrada e saída em awksi, no bloco BEGIN.

    awk 'BEGIN {FS = OFS = "|"} {print $1, $8}' logfile
  • Você pode atribuir essas variáveis ​​ao chamar awkda linha de comando, através do -vsinalizador.

    awk -v 'FS=|' -v 'OFS=|' '{print $1, $8}' logfile
  • ou simplesmente:

    awk -F '|' '{print $1 "|" $8}' logfile
Sparhawk
fonte
4
Bom trabalho, detalhando como esse problema pode ser simplificado. Você pode adicionar uma observação sobre como usar |como um separador de saída, em vez do espaço padrão para concatenação de cadeias. Você também pode explicar como usar, em $NFvez de codificação, $8para obter a última coluna.
21414 Caleb
12

Apenas substitua do primeiro ao último |por um |(ou espaço, se preferir):

sed 's/|.*|/|/'

Note que, embora não exista uma sedimplementação |especial (desde que expressões regulares estendidas não sejam ativadas por meio -Eou -rem algumas implementações), \|ela própria é especial em algumas como o GNU sed. Portanto, você não deve escapar |se pretende que ele corresponda ao |personagem.

Se estiver substituindo por espaço e se a entrada já puder conter linhas com apenas uma |, será necessário tratar isso especialmente como |.*|não corresponderá àquelas. Isso pode ser:

sed 's/|\(.*|\)\{0,1\}/ /'

(ou seja, torne a .*|peça opcional) Ou:

sed 's/|.*|/ /;s/|/ /'

ou:

sed 's/\([^|]*\).*|/\1 /'

Se você deseja o primeiro e o oitavo campos, independentemente do número de campos na entrada, é apenas:

cut -d'|' -f1,8


(todos eles funcionariam com qualquer utilitário compatível com POSIX, assumindo que a entrada forma texto válido (em particular, esses sedgeralmente não funcionarão se a entrada tiver bytes ou sequências de bytes que não formam caracteres válidos no código de idioma atual, por exemplo, printf 'unix|St\351phane|Chazelas\n' | sed 's/|.*|/|/'em uma localidade UTF-8)).

Stéphane Chazelas
fonte
11

Você está usando awkassim mesmo:

awk '{ print $1, $NF }' file
jasonwryan
fonte
2
Você não precisaria especificar o separador do campo de entrada (já que neste caso parece ser |o espaço) com -F\|ou similar? E se ele quisesse usar o mesmo delimitador para saída?
Caleb
@Caleb Provavelmente: Eu estava esperando o OP para confirmar o que exatamente a entrada parecia, ao invés de tentar adivinhar com base nos exemplos não trabalham ...
jasonwryan
1
Observe que isso pressupõe que a entrada contém pelo menos 2 campos.
Stéphane Chazelas
O @ StéphaneChazelas OP afirmou claramente no código que tem oito campos, sempre.
michaelb958 - Restabelece Monica
3
@ michaelb958 eu acho "claramente" está exagerando o caso, apenas um pouco :)
jasonwryan
4

Se você se sentir desajeitado e sem sedas, poderá conseguir o mesmo com os coreutils:

paste <(           cut -d'|' -f1  file) \ 
      <(rev file | cut -d'|' -f1 | rev)
Thor
fonte
cuté mais limpo e mais compacto que o awk / sed quando você está apenas interessado na primeira coluna ou se os delímetros são fixos (ou seja, não é um número variável de espaços).
Sridhar Sarnobat 28/08
2

Parece que você está tentando obter o primeiro e o último campos de texto delimitados por |.

Eu assumi que seu arquivo de log contém o texto como abaixo,

foo|dog|cat|mouse|lion|ox|tiger|bar
bar|dog|cat|mouse|lion|ox|tiger|foo

E você quer a saída como,

foo bar
bar foo

Se sim, então aqui vem o comando para o seu

Através do GNU sed,

sed -r 's~^([^|]*).*\|(.*)$~\1 \2~' file

Exemplo:

$ echo 'foo|dog|cat|mouse|lion|ox|tiger|bar' | sed -r 's~^([^|]*).*\|(.*)$~\1 \2~'
foo bar
Avinash Raj
fonte
As colunas não são delimitadas por um canal | mas eles estão em colunas, estou interessado em usar o sed, mas não o comando awk, como você usou no comando: sed -r's ~ ^ ([^ |] *). * \ | (. *) $ ~ \ Arquivo 1 \ 2 ~ '
user70573
"As colunas não são delimitadas por um pipe | mas estão em colunas", você quer dizer que as colunas são separadas por espaços?
Avinash Raj
Uma entrada de amostra e uma saída seria melhor.
Avinash Raj
1

Você provavelmente deveria fazê-lo com sed- eu faria assim mesmo - mas, apenas porque ninguém escreveu este ainda:

while IFS=\| read col1 cols
do  printf %10s%-s\\n "$col1 |" " ${cols##*|}"
done <<\INPUT
foo|dog|cat|mouse|lion|ox|tiger|bar
INPUT

SAÍDA

     foo | bar
mikeserv
fonte