¿Cómo puedo manipular datos basados en campos desde la línea de comandos? Por ejemplo
- ¿Cómo puedo imprimir solo líneas cuyo campo Nth es
foo
? ? - ¿Cómo puedo imprimir solo líneas cuyo campo N no es
foo
? ? - ¿Cómo puedo imprimir solo líneas cuyo campo N coincide con
foo
? ? - ¿Cómo puedo cambiar el campo N a
foo
? ?
¿Existe un enfoque estándar o un conjunto de herramientas que facilite la manipulación de datos de campo en sistemas *nix?
Respuesta aceptada:
Hay dos enfoques básicos que se pueden usar cuando se trata de campos:i) usar una herramienta que comprenda los campos; ii) utilizar una expresión regular. De los dos, el primero suele ser más robusto y más simple.
Muchas de las herramientas comúnmente disponibles en *nix están diseñadas explícitamente para manejar campos o tienen ingeniosos trucos para facilitarlo.
1. Use una herramienta que comprenda los campos
1.1 mal
La herramienta clásica aquí es awk
. Dividirá automáticamente cada línea de entrada en campos (el separador de campo es un espacio en blanco de forma predeterminada, pero se puede cambiar usando -F
bandera) y los campos están disponibles para el awk
secuencia de comandos como $n
donde n
es el número de campo. El primer campo es $1
, el segundo $2
etc.
-
Imprime líneas cuyo tercer campo es
foo
.awk '$3=="foo"' file
Cambiando el delimitador a
:
awk -F":" '$3=="foo"' file
La acción predeterminada de
awk
es imprimir. Por lo tanto, los comandos anteriores imprimirán todas las líneas cuyo tercer campo seafoo
. Al usar-F
, puede establecer separadores de campo arbitrarios e incluso utilizar expresiones regulares. -
¿Cómo puedo imprimir solo líneas cuyo tercer campo no sea
foo
? ?awk '$3!="foo"' file
-
¿Cómo puedo imprimir solo líneas cuyo tercer campo coincida con
foo
? ?Si solo está buscando campos que coincidan con un patrón (por ejemplo,
foo
coincide confoobar
), utilice~
en lugar de==
:awk '$3~/foo/' file
-
¿Cómo puedo imprimir solo líneas cuyo tercer campo no coincide con
foo
? ?awk '$3!~/foo/' file
-
¿Cómo puedo cambiar el tercer campo a
foo
? ?awk '$3="foo"' file
1.2Perl
Otra opción es perl
de una sola línea. Al igual que awk, Perl es un lenguaje de secuencias de comandos con todas las funciones, pero también se puede ejecutar como un programa de línea de comandos tomando una secuencia de comandos como entrada. Su comportamiento se modifica mediante cambios en la línea de comandos, los más relevantes para esta pregunta son:
-e
:el script queperl
debería ejecutarse;-n
:lee el archivo de entrada línea por línea;-p
:imprime cada línea de entrada después de aplicar el script proporcionado por-e
;-l
:elimine las líneas nuevas finales de cada línea de entrada y agregue una nueva línea a cadaprint
llamar;-a
:modo awk, divide cada línea de entrada en la matriz@F
;-F
:el separador de campo para-a
.
Una diferencia importante con awk
es eso perl
‘s -a
switch divide los archivos en una matriz. En Perl, las matrices comienzan en 0, no en 1. Esto significa que el segundo campo es en realidad $F[1]
y no $F[2]
. Con todo esto en mente, el perl
los equivalentes de los anteriores son:
-
Imprime líneas cuyo tercer campo es
foo
.perl -ane 'print if $F[2] eq "foo"' file
Cambiando el delimitador a
:
perl -F":" -ane 'print if $F[2] eq "foo"' file
A diferencia de
awk
,perl
no puede usar expresiones regulares como delimitadores de campo. Deben ser un carácter o una cadena específicos. -
¿Cómo puedo imprimir solo líneas cuyo tercer campo no sea
foo
? ?perl -ane 'print unless $F[2] eq "foo"' file
-
¿Cómo puedo imprimir solo líneas cuyo tercer campo coincida con
foo
? ?perl -ane 'print if $F[2]=~/foo/' file
-
¿Cómo puedo imprimir solo líneas cuyo tercer campo no coincide con
foo
? ?perl -lane 'print unless $F[2]=~/foo/' file
-
¿Cómo puedo cambiar el tercer campo a
foo
? ?Este es un poco más engorroso en Perl. El enfoque habitual es cambiar el valor en
@F
matriz y luego imprima la matriz. Con archivos simples separados por espacios, esto es fácil:perl -lane '$F[2]="foo"; print "@F"' file
Con un delimitador diferente, deberá
join
la matriz De lo contrario, se imprimirá separado por espacios:perl -F: -lane '$F[2]="foo"; print join ":",@F' file
2. Usa expresiones regulares
La idea aquí es usar una expresión regular ("regex" para abreviar) que define la posición de la cadena de destino en la línea. Por ejemplo, en un archivo cuyos campos están separados por :
, podemos encontrar el segundo campo haciendo coincidir todo hasta el primer :
(el primer campo) y luego buscando el segundo:
^[^:]*:[^:]*:
Esta expresión regular significa:
^
:el comienzo de la línea;[^]
:una clase de carácter negada.[^:]
significa “cualquier cosa menos:
“;*
:0 o más del patrón anterior;:
:un literal:
;
En conjunto, esto significa que el primer [^:]*
es el primer campo y el segundo es el segundo campo. Obviamente, esto no es muy práctico si está buscando el campo 14, pero puede ser útil para cosas más simples. Entonces, ¿cómo implementamos esto para manipular nuestros datos? Hay varias herramientas que pueden hacer esto; en estos ejemplos usaré sed
pero podrías hacer cosas muy similares con awk
, perl
o python
.
-
¿Cómo puedo imprimir solo líneas cuyo segundo campo es
foo
? ?sed -n '/^[^:]*:foo:/p' file
El
-n
suprime la salida normal y el/regex/p
significa "imprimir cualquier línea que coincida con la expresión regular". -
¿Cómo puedo imprimir solo líneas cuyo segundo campo no sea
foo
? ?sed '/^[^:]*:foo:/d' file
El inverso lógico de lo anterior. Aquí, el
/regex/d
significa "eliminar cualquier línea que coincida con la expresión regular". -
¿Cómo puedo imprimir solo líneas cuyo segundo campo coincida con
foo
? ?sed -n '/^[^:]*:[^:]*foo/p' file
-
¿Cómo puedo imprimir solo líneas cuyo segundo campo no coincide con
foo
? ?sed '/^[^:]*:[^:]*foo/d' file
-
¿Cómo puedo cambiar el segundo campo a
foo
? ?sed 's/([^:]*:)[^:]*/1foo/' file
O, desde
sed
la sustitución puede abordar directamente la ocurrencia de un patrón mediante su repetición con un indicador numérico simple:sed 's/[^:]*/foo/2' file