Imprima todas menos las tres primeras columnas

112

Demasiado engorroso:

awk '{print " "$4" "$5" "$6" "$7" "$8" "$9" "$10" "$11" "$12" "$13}' things
hhh
fuente
43
¿Hay alguna razón por la que no pueda usar cut -f3-?
Cascabel
1
@hhh bonito .. Me gusta la idea de una respuesta resumida.
Chris Seymour
2
@Jefromi: porque hay problemas de almacenamiento en búfer de línea con el corte, que awk no tiene: stackoverflow.com/questions/14360640/…
sdaau
@Jefromi: cuttampoco tiene expresiones regulares antes de las {}acciones, y luego es mucho más tonto con los delimitadores de campo (¿número variable de espacios?), Y debe especificarlos manualmente. Creo que el OP quería escuchar sobre algún shift Ncomando, que no existe. El más cercano es $1="";$2="";(...);print}, pero en mi caso deja algunos espacios iniciales (probablemente separadores).
Tomasz Gandor

Respuestas:

50

Una solución que no agrega espacios en blanco adicionales al principio o al final :

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 propone una elegante mejora utilizando el operador ternarioNF?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 ofrece una solución que conserva los espacios en blanco originales entre campos:

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

BinaryZebra también ofrece dos soluciones increíbles:
(estas soluciones incluso conservan los espacios finales de la cadena 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->."

La solución dada por larsr en los comentarios es casi correcta:

$ 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 es la versión fija y parametrizada de la solución 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 las demás respuestas antes de septiembre de 2013 son agradables, pero agregan espacios adicionales:

olibre
fuente
La respuesta de EdMorton no funcionó para mí (bash 4.1.2 (1) -release, GNU Awk 3.1.7 o bash 3.2.25 (1) -release, GNU Awk 3.1.5) pero encontré aquí otra forma:echo ' This is a test' | awk '{print substr($0, index($0,$3))}'
elysch
1
@elysch no, eso no funcionará en general, simplemente parece funcionar dados algunos valores de entrada específicos. Vea el comentario que agregué debajo de su comentario debajo de mi respuesta.
Ed Morton
1
Hola @fedorqui. Mi respuesta es la primera. En mi respuesta original, estaba explicando por qué la otra respuesta no era correcta (espacios en blanco adicionales al principio o al final). Algunas personas han propuesto mejoras en los comentarios. Le hemos pedido al OP que elija una respuesta más correcta y él / ella ha seleccionado la mía. Después de que algunos otros contribuyentes hayan editado mi respuesta para hacer referencia a esa respuesta (consulte el historial). ¿Está claro para ti? ¿Qué me aconseja para mejorar la comprensibilidad de mi respuesta? Saludos ;-)
olibre
1
Tiene toda la razón y lamento mucho mi malentendido. Hice una lectura rápida de la respuesta y no me di cuenta de su respuesta original (sí, leí demasiado rápido). +1 para la respuesta en sí usando el buen truco para hacer un bucle hasta NF-1 y luego imprimir el último elemento para evitar el espacio en blanco adicional. ¡Y lo siento de nuevo! (Eliminaré mi comentario en un día o dos, para evitar malentendidos de futuros lectores).
fedorqui 'SO deja de dañar'
1
Usaría algún tipo de encabezado: <su respuesta> y luego una regla horizontal seguida de un título grande "comparación de las otras respuestas". De lo contrario, mueva esta comparación a otra respuesta, ya que al parecer la gente tiende a preferir respuestas cortas en una visión "dame mi código":)
fedorqui 'SO dejar de hacer daño'
75
awk '{for(i=1;i<4;i++) $i="";print}' file
jiju
fuente
4
Esto dejaría al líder OFSya que no se ocupa, por NFejemplo, del espacio al comienzo en los registros.
Chris Seymour
70

usar corte

$ cut -f4-13 file

o si insiste en awk y $ 13 es el último campo

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

más

$ awk '{for(i=4;i<=13;i++)printf "%s ",$i;printf "\n"}' file
ghostdog74
fuente
14
probablemente sea mejor utilizar "NF" que "13" en el último ejemplo.
glenn jackman
2
2 escenario que corresponde a OP decidir. si 13 es el último campo, usar NF está bien. De lo contrario, usar 13 es apropiado.
ghostdog74
3
2nd necesita eliminar 3 copias de OFS desde el comienzo de $ 0. Tercero sería mejor con printf "%s ",$i, ya que no sabes si $ipodría contener %so algo similar. Pero eso imprimiría un espacio extra al final.
dubiousjim
38

Prueba esto:

awk '{ $1=""; $2=""; $3=""; print $0 }'
lhf
fuente
1
Esto es bueno por lo dinámico que es. Puede agregar columnas al final y no reescribir sus scripts.
MinceMan
1
Esto demuestra el problema exacto con el que la pregunta está tratando de resolver, simplemente haga lo contrario. ¿Qué pasa con la impresión del campo 100? Tenga en cuenta que debe mencionar que no se ocupa de NFlo que deja el liderazgo OFS.
Chris Seymour
24

La forma correcta de hacer esto es con un intervalo RE porque le permite simplemente indicar cuántos campos omitir y conserva el espacio entre campos para los campos restantes.

por ejemplo, para omitir los primeros 3 campos sin afectar el espacio entre los campos restantes, dado el formato de entrada que parece que estamos discutiendo en esta pregunta, es simplemente:

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

Si desea acomodar espacios iniciales y espacios no en blanco, pero nuevamente con el FS predeterminado, entonces es:

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

Si tiene un FS que es un RE que no puede negar en un conjunto de caracteres, puede convertirlo a un solo carácter primero (RS es ideal si es un solo carácter, ya que un RS NO PUEDE aparecer dentro de un campo, de lo contrario, considere SUBSEP), luego aplique la sustitución del intervalo RE, luego conviértalo al OFS. por ejemplo, si las cadenas de "." separan los 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, si OFS es un solo carácter Y no puede aparecer en los campos de entrada, puede reducirlo a:

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

Entonces tiene el mismo problema que con todas las soluciones basadas en bucles que reasignan los campos: los FS se convierten en OFS. Si eso es un problema, necesita buscar en la función patsplit () de GNU awks.

Ed Morton
fuente
No funcionó para mí (bash 4.1.2 (1) -release, GNU Awk 3.1.7 o bash 3.2.25 (1) -release, GNU Awk 3.1.5) pero encontré aquí de otra manera:echo ' This is a test' | awk '{print substr($0, index($0,$3))}'
elysch
2
No, eso fallará si $ 1 o $ 2 contienen la cadena en la que se establece $ 3. Pruebe, por ejemplo, echo ' That is a test' | awk '{print substr($0, index($0,$3))}'y encontrará que el avalor de $ 3 coincide con el ainterior Thatde $ 1. En una versión muy antigua de gawk como la que tiene, debe habilitar los intervalos RE con la bandera --re-interval.
Ed Morton
2
Tienes razón, no me di cuenta. Por cierto, realmente agradezco tu comentario. Muchas veces quise usar una expresión regular con "{}" para especificar el número de elementos y nunca vi "--re-interval" en el man. +1 para ti.
elysch
1
1es una condición verdadera y por lo tanto invoca la acción awk predeterminada de imprimir el registro actual.
Ed Morton
1
No sé lo canónico que es, pero agregué una respuesta ahora.
Ed Morton
10

Prácticamente todas las respuestas actualmente agregan espacios iniciales, espacios finales o algún otro problema de separador. Para seleccionar desde el cuarto campo donde el separador es un espacio en blanco y el separador de salida es un espacio único, el uso awksería:

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

Para parametrizar el campo de inicio puede hacer:

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

Y también el 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
fuente
6
awk '{$1=$2=$3="";$0=$0;$1=$1}1'

Entrada

1 2 3 4 5 6 7

Salida

4 5 6 7

fuente
4
echo 1 2 3 4 5| awk '{ for (i=3; i<=NF; i++) print $i }'
Vetsin
fuente
3
O para ponerlos en la misma línea, asigne $ 3 a $ 1, etc. y luego cambie NF al número correcto de campos. echo 1 2 3 4 5| awk '{ for (i=3; i<=NF; i++) $(i-2)=$i; NF=NF-2; print $0 }'
larsr
Hola @larsr. Su línea de comando propuesta es la única respuesta correcta. Todas las demás respuestas agregan espacios adicionales (iniciales o finales). Por favor publique su línea de comando dentro de una nueva respuesta, la votaré ;-)
olibre
1
Hola @sudo_O, estaba hablando con @larsr, sobre la línea de comando que propuso en su comentario. Pasé unos cinco minutos antes de descubrir el quiproco (malentendido). Estoy de acuerdo, la respuesta de @Vetsin inserta nuevas líneas ( ORS) entre los campos. Bravo por tu iniciativa (me gusta tu respuesta). Saludos
olibre
3

Otra forma de evitar el uso de la declaración de impresión:

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

En awk, cuando una condición es verdadera, la impresión es la acción predeterminada.

Juan Diego Godoy Robles
fuente
Esto tiene todos los problemas que tiene @lhf answer ... es más corto.
Chris Seymour
Muy buena idea;) ¡Mejor que mi respuesta! (Ya voté a favor de tu respuesta el año pasado) Saludos
olibre
Debería ser: awk '{$1=$2=$3=""}sub("^"OFS"+","")' filecomo es el OFS lo que queda después de cambiar los contenidos de $ 1, $ 2 y $ 3.
3

No puedo creer que nadie ofreciera una cáscara simple:

while read -r a b c d; do echo "$d"; done < file
Glenn Jackman
fuente
+1 para la solución similar ... Pero esto puede tener problemas de rendimiento si filees grande (> 10-30 KB). Para archivos grandes, la awksolución funciona mejor.
TrueY
3

Las opciones 1 a 3 tienen problemas con varios espacios en blanco (pero son simples). Esa es la razón para desarrollar las opciones 4 y 5, que procesan múltiples espacios en blanco sin problemas. Por supuesto, si se utilizan las opciones 4 o 5 con n=0ambas, se conservarán los espacios en blanco iniciales, lo que n=0significa que no habrá división.

Opción 1

Una solución de corte simple (funciona con delimitadores simples):

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

opcion 2

Forzar una recalculación de awk a veces resuelve el problema (funciona con algunas versiones de awk) de espacios iniciales agregados:

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

Opción 3

Imprimir cada campo formateado con printfle dará más control:

$ 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

Sin embargo, todas las respuestas anteriores cambian todos los FS entre campos a OFS. Construyamos un par de soluciones para eso.

Opción 4

Un bucle con sub para eliminar campos y delimitadores es más portátil y no activa un cambio de FS a 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: El "^ [" FS "] *" es para aceptar una entrada con espacios iniciales.

Opcion 5

Es muy posible construir una solución que no agregue espacios en blanco adicionales al principio o al final, y preservar los espacios en blanco existentes usando la función gensubde GNU awk, como esta:

$ 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 

También se puede usar para intercambiar una lista de campos dado un recuento 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     !

Por supuesto, en tal caso, el OFS se utiliza para separar ambas partes de la línea y el espacio en blanco final de los campos aún se imprime.

Nota 1: ["FS"]* se utiliza para permitir espacios iniciales en la línea de entrada.


fuente
Hola BZ Tu respuesta es agradable. Pero la Opción 3 no funciona en cadenas que comienzan con un espacio (p " 1 2 3 4 5 6 7 8 ". Ej .). La opción 4 es buena, pero deja un espacio inicial usando una cadena que comience con un espacio. ¿Crees que esto se puede arreglar? Puede usar el comando echo " 1 2 3 4 5 6 7 8 " | your awk script | sed 's/ /./g;s/\t/->/g;s/^/"/;s/$/"/'para verificar los espacios
iniciales
Hola @olibre. Que la opción 3 falle con espacios en blanco es la razón para desarrollar las opciones 4 y 5. La opción 4 solo deja un espacio inicial si la entrada lo tiene y n se establece en 0 (n = 0). Creo que es la respuesta correcta cuando no hay una selección de campos (nada que arreglar en mi opinión). Salud.
Todo bien. Gracias por la información adicional :-) Por favor mejore su respuesta proporcionando esta información adicional :-) Saludos
olibre
Perfecto :-) Qué lástima que su usuario esté deshabilitado :-(
olibre
1

Cut tiene una marca --complement que hace que sea fácil (y rápido) eliminar columnas. La sintaxis resultante es análoga a lo que desea hacer, lo que hace que la solución sea más fácil de leer / comprender. El complemento también funciona para el caso en el que desee eliminar columnas no contiguas.

$ foo='1 2 3 %s 5 6 7'
$ echo "$foo" | cut --complement -d' ' -f1-3
%s 5 6 7
$
Michael Back
fuente
¿Puede explicar más su respuesta por favor?
Zulu
¿La edición anterior ayuda a comprender? El punto es utilizar la bandera de corte complementaria. La solución debe ser una implementación más rápida y concisa que las soluciones basadas en AWK o perl. Además, se pueden cortar columnas arbitrarias.
Michael Back
1

Solución Perl que no agrega espacios en blanco iniciales o finales:

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

La @Fmatriz perl autosplit comienza en el índice, 0mientras que los campos awk comienzan con$1


Solución Perl para datos delimitados por comas:

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

Solución de Python:

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

Chris Koknat
fuente
0

Para mí, la solución más compacta y compatible con la solicitud es

$ 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}'

Y si tiene más líneas para procesar como, por ejemplo, el archivo foo.txt , no olvide restablecer i a 0:

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

Gracias tu foro.

user8008888
fuente
0

Como me molestó la primera respuesta altamente votada pero incorrecta, encontré suficiente para escribir una respuesta allí, y aquí las respuestas incorrectas están marcadas como tales, aquí está mi parte. No me gustan las soluciones propuestas, ya que no veo ninguna razón para hacer que la respuesta sea tan compleja.

Tengo un registro donde después de $ 5 con una dirección IP puede haber más texto o ningún texto. Necesito todo, desde la dirección IP hasta el final de la línea, en caso de que haya algo después de $ 5. En mi caso, esto es realmente dentro de un programa awk, no un delineador awk, por lo que awk debe resolver el problema. Cuando trato de eliminar los primeros 4 campos usando la respuesta antigua, bonita y con más votos positivos, pero completamente incorrecta:

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}'

escupe una respuesta incorrecta e inútil (agregué [] para demostrar):

[    37.244.182.218 one two three]

En cambio, si las columnas tienen un ancho fijo hasta que se necesitan el punto de corte y awk, la respuesta correcta y bastante simple es:

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

que produce la salida deseada:

[37.244.182.218 one two three]
Pila
fuente
0

Encontré esta otra posibilidad, quizás también podría ser útil ...

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

Nota: 1. Para datos tabulares y de la columna $ 1 a $ 14

jgarces
fuente
0

Usar corte:

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

por ejemplo: si tiene file1conteniendo:car.is.nice.equal.bmw

Ejecutar: cut -d . -f1,3 file1 imprimirácar.is.nice

zayed
fuente
Parece que su solución podría estar al revés. Por favor revise el título de la pregunta Imprima todo * menos * las primeras tres columnas
Stefan Crain
-1

Esto no está muy lejos de algunas de las respuestas anteriores, pero resuelve un par de problemas:

cols.sh:

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

Que ahora puedes llamar con un argumento que será la columna de inicio:

$ 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

O:

$ 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

Esto está indexado a 1; si prefiere cero indexado, utilice i=s + 1en su lugar.

Además, si desea tener argumentos para el índice inicial y el índice final, cambie el archivo a:

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

Por ejemplo:

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

Las %-5salinea el resultado como columnas de 5 caracteres de ancho; si esto no es suficiente, aumente el número o use %s(con un espacio) en su lugar si no le importa la alineación.

usuario2141650
fuente
-1

Solución AWK basada en printf que evita% de problemas y es única en el sentido de que no devuelve nada (sin carácter de retorno) si hay menos de 4 columnas para imprimir:

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

Pruebas:

$ 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
fuente