Imprime tudo, exceto as três primeiras colunas

112

Muito complicado:

awk '{print " "$4" "$5" "$6" "$7" "$8" "$9" "$10" "$11" "$12" "$13}' things
hhh
fonte
43
Existe alguma razão pela qual você não pode simplesmente usar cut -f3-?
Cascabel
1
@hhh boa .. Eu gosto da ideia de uma resposta resumida.
Chris Seymour
2
@Jefromi - porque há problemas de buffer de linha com corte, que o awk não tem: stackoverflow.com/questions/14360640/…
sdaau
2
possível duplicata de Usando awk para imprimir todas as colunas do enésimo ao último
Wladimir Palant
@Jefromi - também cutnão tem regexes antes de {}ações, e é muito mais burro com delimitadores de campo (número variável de espaços?), E você tem que especificá-los manualmente. Acho que o OP queria ouvir sobre algum shift Ncomando, que não existe. O mais próximo é $1="";$2="";(...);print}, mas no meu caso deixa alguns espaços à esquerda (provavelmente separadores).
Tomasz Gandor

Respostas:

50

Uma solução que não adiciona espaço em branco extra à esquerda ou à direita :

awk '{ for(i=4; i<NF; i++) printf "%s",$i OFS; if(NF) printf "%s",$NF; printf ORS}'

### Example ###
$ echo '1 2 3 4 5 6 7' |
  awk '{for(i=4;i<NF;i++)printf"%s",$i OFS;if(NF)printf"%s",$NF;printf ORS}' |
  tr ' ' '-'
4-5-6-7

Sudo_O propõe uma melhoria elegante usando o operador ternárioNF?ORS:OFS

$ echo '1 2 3 4 5 6 7' |
  awk '{ for(i=4; i<=NF; i++) printf "%s",$i (i==NF?ORS:OFS) }' |
  tr ' ' '-'
4-5-6-7

EdMorton oferece uma solução que preserva os espaços em branco originais entre os campos:

$ echo '1   2 3 4   5    6 7' |
  awk '{ sub(/([^ ]+ +){3}/,"") }1' |
  tr ' ' '-'
4---5----6-7

BinaryZebra também oferece duas soluções impressionantes:
(essas soluções preservam até mesmo espaços à direita da string original)

$ echo -e ' 1   2\t \t3     4   5   6 7 \t 8\t ' |
  awk -v n=3 '{ for ( i=1; i<=n; i++) { sub("^["FS"]*[^"FS"]+["FS"]+","",$0);} } 1 ' |
  sed 's/ /./g;s/\t/->/g;s/^/"/;s/$/"/'
"4...5...6.7.->.8->."

$ echo -e ' 1   2\t \t3     4   5   6 7 \t 8\t ' |
  awk -v n=3 '{ print gensub("["FS"]*([^"FS"]+["FS"]+){"n"}","",1); }' |
  sed 's/ /./g;s/\t/->/g;s/^/"/;s/$/"/'
"4...5...6.7.->.8->."

A solução dada por larsr nos comentários é quase correta:

$ echo '1 2 3 4 5 6 7' | 
  awk '{for (i=3;i<=NF;i++) $(i-2)=$i; NF=NF-2; print $0}' | tr  ' ' '-'
3-4-5-6-7

Esta é a versão fixa e parametrizada da solução larsr :

$ echo '1 2 3 4 5 6 7' | 
  awk '{for(i=n;i<=NF;i++)$(i-(n-1))=$i;NF=NF-(n-1);print $0}' n=4 | tr ' ' '-'
4-5-6-7

Todas as outras respostas antes de setembro de 2013 são boas, mas adicione espaços extras:

Olibre
fonte
A resposta de EdMorton não funcionou para mim (bash 4.1.2 (1) -release, GNU Awk 3.1.7 ou bash 3.2.25 (1) -release, GNU Awk 3.1.5), mas encontrada aqui de outra forma:echo ' This is a test' | awk '{print substr($0, index($0,$3))}'
elysch
1
@elysch não, isso não funcionará em geral, apenas parece funcionar dados alguns valores de entrada específicos. Veja o comentário que adicionei abaixo do seu comentário em minha resposta.
Ed Morton
1
Oi @fedorqui. Minha resposta é a primeira. Em minha resposta original, eu estava explicando por que a outra resposta não estava correta (espaço em branco extra à esquerda ou à direita). Algumas pessoas propuseram melhorias nos comentários. Pedimos ao OP que escolhesse uma resposta mais correta e ele escolheu a minha. Depois de alguns outros colaboradores terem editado minha resposta para fazer referência a essa resposta (veja o histórico). Está claro para você? O que você me aconselha para melhorar a compreensão da minha resposta? Saúde ;-)
olibre
1
Você está absolutamente certo e lamento muito o meu mal-entendido. Eu li a resposta rapidamente e não percebi sua resposta original (sim, eu li muito rápido). +1 para a própria resposta usando o bom truque para fazer um loop até NF-1 e depois imprimir o último elemento para evitar os espaços em branco extras. E desculpe novamente! (removerá meu comentário em um ou dois dias, para evitar mal-entendidos de leitores futuros).
fedorqui 'ASSIM, pare de prejudicar'
1
Eu usaria algum tipo de cabeçalho: <sua resposta> e, em seguida, uma regra horizontal seguida por um grande título "comparação das outras respostas". Caso contrário, mova esta comparação para outra resposta, já que aparentemente as pessoas tendem a preferir respostas curtas em uma visão "me dê meu código"
:)
75
awk '{for(i=1;i<4;i++) $i="";print}' file
Jiju
fonte
4
Isso deixaria entrelinha OFSporque você não lida com NFespaço de entrelinha nos registros.
Chris Seymour
70

usar corte

$ cut -f4-13 file

ou se você insiste em awk e $ 13 é o último campo

$ awk '{$1=$2=$3="";print}' file

outro

$ awk '{for(i=4;i<=13;i++)printf "%s ",$i;printf "\n"}' file
ghostdog74
fonte
14
provavelmente melhor usar "NF" do que "13" no último exemplo.
glenn jackman
2
2 cenários que cabem ao OP decidir. se 13 for o último campo, usar NF está certo. Caso contrário, usar 13 é apropriado.
ghostdog74
3
2 ° precisa excluir 3 cópias do OFS do início de $ 0. 3º seria melhor com printf "%s ",$i, já que você não sabe se $ipode conter %sou algo semelhante. Mas isso imprimiria um espaço extra no final.
duvidosojim
38

Experimente isto:

awk '{ $1=""; $2=""; $3=""; print $0 }'
lhf
fonte
1
Isso é bom por causa de sua dinâmica. Você pode adicionar colunas no final e não reescrever seus scripts.
MinceMan
1
Isso demonstra o problema exato com que a pergunta está tentando lidar, basta fazer o oposto. Que tal imprimir o do 100º campo? Nota para mencionar que você não lida com NFisso deixando de liderar OFS.
Chris Seymour
24

A maneira correta de fazer isso é com um intervalo RE, porque ele permite que você simplesmente informe quantos campos devem ser ignorados e retém o espaçamento entre campos para os campos restantes.

por exemplo, para pular os 3 primeiros campos sem afetar o espaçamento entre os campos restantes, dado o formato de entrada que parecemos estar discutindo nesta questão é simplesmente:

$ echo '1   2 3 4   5    6' |
awk '{sub(/([^ ]+ +){3}/,"")}1'
4   5    6

Se você deseja acomodar espaços à esquerda e não em branco, mas novamente com o FS padrão, é:

$ echo '  1   2 3 4   5    6' |
awk '{sub(/[[:space:]]*([^[:space:]]+[[:space:]]+){3}/,"")}1'
4   5    6

Se você tem um FS que é um RE que você não pode negar em um conjunto de caracteres, você pode convertê-lo em um único caractere primeiro (RS é ideal se for um único caractere, pois um RS NÃO PODE aparecer dentro de um campo, caso contrário, considere SUBSEP), em seguida, aplique a substituição do intervalo de RE e, em seguida, converta para o OFS. por exemplo, se cadeias de "." s separassem os campos:

$ echo '1...2.3.4...5....6' |
awk -F'[.]+' '{gsub(FS,RS);sub("([^"RS"]+["RS"]+){3}","");gsub(RS,OFS)}1'
4 5 6

Obviamente, se OFS é um único caractere E não pode aparecer nos campos de entrada, você pode reduzir isso para:

$ echo '1...2.3.4...5....6' |
awk -F'[.]+' '{gsub(FS,OFS); sub("([^"OFS"]+["OFS"]+){3}","")}1'
4 5 6

Então você tem o mesmo problema que com todas as soluções baseadas em loop que reatribuem os campos - os FSs são convertidos em OFSs. Se isso for um problema, você precisa examinar a função patsplit () do GNU awks.

Ed Morton
fonte
Não funcionou para mim (bash 4.1.2 (1) -release, GNU Awk 3.1.7 ou bash 3.2.25 (1) -release, GNU Awk 3.1.5) mas encontrei aqui de outra forma:echo ' This is a test' | awk '{print substr($0, index($0,$3))}'
elysch
2
Não, isso falhará se $ 1 ou $ 2 contiverem a string para a qual $ 3 está definido. Tente, por exemplo, echo ' That is a test' | awk '{print substr($0, index($0,$3))}'e você descobrirá que o aque é $ 3 corresponde ao ainterior Thatem $ 1. Em uma versão muito antiga do gawk como a sua, você precisa ativar os intervalos RE com o sinalizador --re-interval.
Ed Morton
2
Você está certo, não percebeu. A propósito, realmente aprecio seu comentário. Muitas vezes quis usar uma regex com "{}" para especificar o número de elementos e nunca vi "--re-interval" no homem. 1 para você.
elysch
1
1é uma condição verdadeira e, portanto, invoca a ação padrão awk de imprimir o registro atual.
Ed Morton
1
idk quão canônico é, mas eu adicionei uma resposta agora.
Ed Morton
10

Quase todas as respostas adicionam espaços iniciais, espaços finais ou algum outro problema de separação. Para selecionar a partir do quarto campo onde o separador é um espaço em branco e o separador de saída é um único espaço, usando awkseria:

awk '{for(i=4;i<=NF;i++)printf "%s",$i (i==NF?ORS:OFS)}' file

Para parametrizar o campo inicial, você pode fazer:

awk '{for(i=n;i<=NF;i++)printf "%s",$i (i==NF?ORS:OFS)}' n=4 file

E também o campo final:

awk '{for(i=n;i<=m=(m>NF?NF:m);i++)printf "%s",$i (i==m?ORS:OFS)}' n=4 m=10 file
Chris Seymour
fonte
6
awk '{$1=$2=$3="";$0=$0;$1=$1}1'

Entrada

1 2 3 4 5 6 7

Resultado

4 5 6 7

fonte
4
echo 1 2 3 4 5| awk '{ for (i=3; i<=NF; i++) print $i }'
Vetsin
fonte
3
Ou para colocá-los na mesma linha, atribua $ 3 a $ 1, etc. e, em seguida, altere a NF para o número correto de campos. echo 1 2 3 4 5| awk '{ for (i=3; i<=NF; i++) $(i-2)=$i; NF=NF-2; print $0 }'
larsr
Olá @larsr. Sua linha de comando proposta é a única resposta correta. Todas as outras respostas adicionam espaços extras (à esquerda ou à direita). Por favor, poste sua linha de comando em uma nova resposta, eu irei votar a favor ;-)
olibre
1
Olá @sudo_O, estava falando com @larsr sobre a linha de comando que ele propôs em seu comentário. Passei cerca de cinco minutos antes de descobrir o quiproco (mal-entendido). Concordo, a resposta @Vetsin insere novas linhas ( ORS) entre os campos. Bravo pela sua iniciativa (gosto da sua resposta). Saúde
olibre
3

Outra maneira de evitar o uso da instrução de impressão:

 $ awk '{$1=$2=$3=""}sub("^"FS"*","")' file

No awk, quando uma condição é verdadeira, imprimir é a ação padrão.

Juan Diego Godoy Robles
fonte
Isso tem todos os problemas que a resposta @lhf tem .. é apenas mais curto.
Chris Seymour
Muito boa ideia;) Melhor do que a minha resposta! (Já votei a favor da sua resposta no ano passado) Saúde
olibre
Deve ser: assim awk '{$1=$2=$3=""}sub("^"OFS"+","")' filecomo o OFS, o que resta depois de alterar o conteúdo de $ 1, $ 2 e $ 3.
3

Não acredito que ninguém ofereceu uma casca simples:

while read -r a b c d; do echo "$d"; done < file
glenn jackman
fonte
1 para a solução semelhante ... Mas isso pode ter problemas de desempenho se filefor grande (> 10-30 KiB). Para arquivos grandes, a awksolução tem um desempenho melhor.
TrueY
3

As opções 1 a 3 apresentam problemas com vários espaços em branco (mas são simples). Essa é a razão para desenvolver as opções 4 e 5, que processam vários espaços em branco sem nenhum problema. Obviamente, se as opções 4 ou 5 forem usadas com n=0ambas, os espaços em branco à esquerda serão preservados, o que n=0significa que não haverá divisão.

Opção 1

Uma solução de corte simples (funciona com delimitadores simples):

$ echo '1 2 3 4 5 6 7 8' | cut -d' ' -f4-
4 5 6 7 8

opção 2

Forçar um recálculo do awk às vezes resolve o problema (funciona com algumas versões do awk) de espaços iniciais adicionados:

$ echo '1 2 3 4 5 6 7 8' | awk '{ $1=$2=$3="";$0=$0;} NF=NF'
4 5 6 7 8

Opção 3

Imprimir cada campo formatado com printfdará mais controle:

$ echo '    1    2  3     4   5   6 7     8  ' |
  awk -v n=3 '{ for (i=n+1; i<=NF; i++){printf("%s%s",$i,i==NF?RS:OFS);} }'
4 5 6 7 8

No entanto, todas as respostas anteriores alteram todos os FS entre os campos para OFS. Vamos construir algumas soluções para isso.

Opção 4

Um loop com sub para remover campos e delimitadores é mais portátil e não aciona uma mudança de FS para OFS:

$ echo '    1    2  3     4   5   6 7     8  ' |
awk -v n=3 '{ for(i=1;i<=n;i++) { sub("^["FS"]*[^"FS"]+["FS"]+","",$0);} } 1 '
4   5   6 7     8

NOTA: O "^ [" FS "] *" é para aceitar uma entrada com espaços à esquerda.

Opção 5

É bem possível construir uma solução que não adicione espaços em branco extras à esquerda ou à direita e preserve os espaços em branco existentes usando a função gensubdo GNU awk, como este:

$ echo '    1    2  3     4   5   6 7     8  ' |
awk -v n=3 '{ print gensub("["FS"]*([^"FS"]+["FS"]+){"n"}","",1); }'
4   5   6 7     8 

Também pode ser usado para trocar uma lista de campos dada uma contagem n:

$ echo '    1    2  3     4   5   6 7     8  ' |
  awk -v n=3 '{ a=gensub("["FS"]*([^"FS"]+["FS"]+){"n"}","",1);
                b=gensub("^(.*)("a")","\\1",1);
                print "|"a"|","!"b"!";
               }'
|4   5   6 7     8  | !    1    2  3     !

Obviamente, nesse caso, o OFS é usado para separar as duas partes da linha e o espaço em branco posterior dos campos ainda é impresso.

Nota1: ["FS"]* é usado para permitir espaços à esquerda na linha de entrada.


fonte
Oi BZ Sua resposta é boa. Mas a opção 3 não funciona em strings que começam com um espaço (por exemplo " 1 2 3 4 5 6 7 8 "). A opção 4 é boa, mas deixe um espaço inicial usando uma string começando com um espaço. Você acha que isso pode ser corrigido? Você pode usar o comando a echo " 1 2 3 4 5 6 7 8 " | your awk script | sed 's/ /./g;s/\t/->/g;s/^/"/;s/$/"/'fim de verificar os espaços à esquerda / meio / à direita ... Saúde;)
olibre
Olá @olibre. Que a opção 3 falhe com espaço em branco é a razão para desenvolver as opções 4 e 5. A opção 4 só deixa um espaço à esquerda se a entrada o tiver e n for definido como 0 (n = 0). Essa eu acredito ser a resposta correta quando não há seleção de campos (nada para consertar IMO). Felicidades.
Tudo certo. Obrigado pelas informações adicionais :-) Por favor, melhore sua resposta fornecendo estas informações extras :-) Saudações
olibre
Perfeito :-) Que pena que seu usuário está desativado :-(
olibre
1

Cut tem um sinalizador --complement que torna mais fácil (e rápido) excluir colunas. A sintaxe resultante é análoga ao que você deseja fazer - tornando a solução mais fácil de ler / entender. O complemento também funciona para o caso em que você deseja excluir colunas não contíguas.

$ foo='1 2 3 %s 5 6 7'
$ echo "$foo" | cut --complement -d' ' -f1-3
%s 5 6 7
$
Michael Back
fonte
Você pode explicar mais sua resposta, por favor?
Zulu
A edição acima ajuda na compreensão? O objetivo é usar o sinalizador de corte de complemento. A solução deve ser uma implementação mais rápida e concisa do que as soluções baseadas em AWK ou perl. Além disso, colunas arbitrárias podem ser cortadas.
Michael Back
1

Solução Perl que não adiciona espaços em branco à esquerda ou à direita:

perl -lane 'splice @F,0,3; print join " ",@F' file

O @Farray perl autosplit começa no índice 0enquanto os campos awk começam com$1


Solução Perl para dados delimitados por vírgulas:

perl -F, -lane 'splice @F,0,3; print join ",",@F' file

Solução Python:

python -c "import sys;[sys.stdout.write(' '.join(line.split()[3:]) + '\n') for line in sys.stdin]" < file

Chris Koknat
fonte
0

Para mim, a solução mais compacta e compatível com o pedido é

$ a='1   2\t \t3     4   5   6 7 \t 8\t '; 
$ echo -e "$a" | awk -v n=3 '{while (i<n) {i++; sub($1 FS"*", "")}; print $0}'

E se você tiver mais linhas para processar, como por exemplo o arquivo foo.txt , não se esqueça de redefinir i para 0:

$ awk -v n=3 '{i=0; while (i<n) {i++; sub($1 FS"*", "")}; print $0}' foo.txt

Obrigado ao seu fórum.

user8008888
fonte
0

Como fiquei aborrecido com a primeira resposta muito votada, mas errada, encontrei o suficiente para escrever uma resposta lá, e aqui as respostas erradas estão marcadas como tal, aqui está a minha parte. Não gosto de soluções propostas, pois não vejo razão para tornar a resposta tão complexa.

Eu tenho um log onde depois de $ 5 com um endereço IP pode haver mais texto ou nenhum texto. Preciso de tudo, desde o endereço IP até o final da linha, caso haja algo depois de $ 5. No meu caso, isso está na verdade em um programa awk, não em um awk oneliner, então o awk deve resolver o problema. Quando tento remover os primeiros 4 campos usando a resposta antiga, de boa aparência e mais votada, mas completamente errada:

echo "  7 27.10.16. Thu 11:57:18 37.244.182.218 one two three" | awk '{$1=$2=$3=$4=""; printf "[%s]\n", $0}'

ele cospe uma resposta errada e inútil (acrescentei [] para demonstrar):

[    37.244.182.218 one two three]

Em vez disso, se as colunas têm largura fixa até o ponto de corte e o awk é necessário, a resposta correta e bastante simples é:

echo "  7 27.10.16. Thu 11:57:18 37.244.182.218 one two three" | awk '{printf "[%s]\n", substr($0,28)}'

que produz a saída desejada:

[37.244.182.218 one two three]
Pila
fonte
0

Encontrei essa outra possibilidade, talvez possa ser útil também ...

awk 'BEGIN {OFS=ORS="\t" }; {for(i=1; i<14; i++) print $i " "; print $NF "\n" }' your_file

Nota: 1. Para dados tabulares e da coluna $ 1 a $ 14

jgarces
fonte
0

Use corte:

cut -d <The character between characters> -f <number of first column>,<number of last column> <file name>

por exemplo: se você file1contém:car.is.nice.equal.bmw

Executar: cut -d . -f1,3 file1 irá imprimircar.is.nice

zayed
fonte
Parece que sua solução pode estar invertida. Revise o título da pergunta. Imprima todas * exceto * as três primeiras colunas
Stefan Crain
-1

Isso não está muito longe de algumas das respostas anteriores, mas resolve alguns problemas:

cols.sh:

#!/bin/bash
awk -v s=$1 '{for(i=s; i<=NF;i++) printf "%-5s", $i; print "" }'

Que você agora pode chamar com um argumento que será a coluna inicial:

$ echo "1 2 3 4 5 6 7 8 9 10 11 12 13 14" | ./cols.sh 3 
3    4    5    6    7    8    9    10   11   12   13   14

Ou:

$ echo "1 2 3 4 5 6 7 8 9 10 11 12 13 14" | ./cols.sh 7 
7    8    9    10   11   12   13   14

Este é indexado 1; se você preferir indexação zero, use i=s + 1.

Além disso, se você gostaria de ter argumentos para o índice inicial e índice final, altere o arquivo para:

#!/bin/bash
awk -v s=$1 -v e=$2 '{for(i=s; i<=e;i++) printf "%-5s", $i; print "" }'

Por exemplo:

$ echo "1 2 3 4 5 6 7 8 9 10 11 12 13 14" | ./cols.sh 7 9 
7    8    9

O %-5salinha o resultado como colunas de 5 caracteres de largura; se isso não for suficiente, aumente o número ou use %s(com um espaço) se você não se importar com o alinhamento.

user2141650
fonte
-1

Solução baseada em AWK printf que evita% problema e é única por não retornar nada (nenhum caractere de retorno) se houver menos de 4 colunas para imprimir:

awk 'NF > 3 { for(i=4; i<NF; i++) printf("%s ", $(i)); print $(i) }'

Teste:

$ x='1 2 3 %s 4 5 6'
$ echo "$x" | awk 'NF > 3 { for(i=4; i<NF; i++) printf("%s ", $(i)); print $(i) }'
%s 4 5 6
$ x='1 2 3'
$ echo "$x" | awk 'NF > 3 { for(i=4; i<NF; i++) printf("%s ", $(i)); print $(i) }'
$ x='1 2 3 '
$ echo "$x" | awk 'NF > 3 { for(i=4; i<NF; i++) printf("%s ", $(i)); print $(i) }'
$
Michael Back
fonte