¿Cómo estais? aquí Kiko. Os traigo un nuevo post sobre el sistema operativo mas molón. Encantado linuxeros.
Cuando se trata de dividir un archivo de texto en varios archivos en Linux, la mayoría de la gente usa el comando split. No hay nada de malo con el comando split, excepto que depende del tamaño del byte o del tamaño de la línea para dividir los archivos.
Esto no es conveniente en situaciones en las que necesita dividir archivos en función de su contenido en lugar de su tamaño. Dejame darte un ejemplo.
Administro mis tweets planificados usando ЯМЛ archivos. Un archivo de tweet típico contiene varios tweets separados por cuatro guiones:
----
event:
repeat: days: 180
status: |
I think I use the `sed` command daily. And you?
https://www.yesik.it/EP07
#Shell #Linux #Sed #YesIKnowIT
----
status: |
Print the first column of a space-separated data file:
awk 'print $1' data.txt # Print out just the first column
For some unknown reason, I find that easier to remember than:
cut -f1 data.txt
#Linux #AWK #Cut
----
status: |
For the #shell #beginners :
[...]
Cuando los importo a mi sistema, tengo que escribir cada tweet en mi propio archivo. Hago esto para evitar registrar tweets duplicados.
Pero, ¿cómo divido un archivo en varias partes en función de su contenido? Bueno, probablemente puedas obtener algo convincente usando los comandos awk:
sh$ awk < tweets.yaml '
> /----/ OUTPUT="tweet." (N++) ".yaml"
> print > OUTPUT
> '
A pesar de su relativa simplicidad, dicha solución no es muy estable: por ejemplo, no cerré los diversos archivos fuente correctamente, por lo que esto puede alcanzar en gran medida el límite de archivos abiertos. ¿O qué pasa si olvidé el separador antes del primer tweet del archivo? Por supuesto, todo esto se puede procesar y ajustar en el script AWK, a costa de hacerlo más complejo. Pero, ¿por qué lidiar con eso cuando tenemos csplit
herramienta para realizar esta tarea?
Use csplit para dividir archivos en Linux
El csplit
la herramienta es prima de split
herramienta que se puede utilizar para dividir un archivo en piezas de tamaño fijo. Pero csplit
identificará los límites de las piezas en función del contenido del archivo, en lugar de utilizar el número de bytes.
En este tutorial, demostraré el uso de los comandos csplit y explicaré el resultado de este comando.
Por ejemplo, si quiero dividir mi archivo de tweets en función de ----
separador, podría escribir:
sh$ csplit tweets.yaml /----/
0
10846
Puede que hayas adivinado csplit
La herramienta utiliza la expresión regular proporcionada en la línea de comando para identificar el delimitador. Y que podrian ser estos 0
y 10983
resultado mostrado en la salida estándar? Bueno, tienen el tamaño en bytes de cada dato creado.
sh$ ls -l xx0*
-rw-r--r-- 1 sylvain sylvain 0 Jun 6 11:30 xx00
-rw-r--r-- 1 sylvain sylvain 10846 Jun 6 11:30 xx01
¡Espera un minuto! Donde estos xx00
y xx01
los nombres de archivo provienen? Y por qué csplit
dividir el archivo en solo dos partes? ¿Y por qué el primer dato tiene una longitud de cero bytes?
La respuesta a la primera pregunta es simple: xxNN
(o más formalmente xx%02d
) es el formato de nombre de archivo predeterminado utilizado por csplit
. Pero puedes cambiar eso usando --suffix-format
y --prefix
ajustes. Por ejemplo, podría cambiar la forma de algo más significativo para mis necesidades:
sh$ csplit tweets.yaml
> --prefix='tweet.' --suffix-format="%03d.yaml"
> /----/
0
10846
sh$ ls -l tweet.*
-rw-r--r-- 1 sylvain sylvain 0 Jun 6 11:30 tweet.000.yaml
-rw-r--r-- 1 sylvain sylvain 10846 Jun 6 11:30 tweet.001.yaml
El prefijo es una cadena simple, pero el sufijo es una cadena en un formato como el que usa la biblioteca C estándar. printf
función. La mayoría de los caracteres de formulario se usarán literalmente, excepto para las especificaciones de conversión, que se ingresan con el signo de porcentaje (%
) y que termina con un especificador de conversión (aquí, d
). Entre ellos, el formato también puede contener varias banderas y opciones. En mi ejemplo %03d
la especificación de conversión significa:
muestra el número de pista como un entero decimal (d
), en un campo de tres caracteres (3
), eventualmente alineado a la izquierda con ceros (0
).
Pero eso no se aplica a las otras interrogaciones que tuve anteriormente: ¿por qué solo tenemos dos pistas, una de las cuales contiene cero bytes? Es posible que ya haya encontrado la respuesta a la última pregunta: mi archivo de datos comienza con ----
en su primera fila. Entonces, csplit
lo miró como un separador, y como no había datos antes de esa línea, creó una primera pieza vacía. Podemos deshabilitar la creación de archivos de longitud de cero bytes usando --elide-empty-files
opción:
sh$ rm tweet.*
rm: cannot remove 'tweet.*': No such file or directory
sh$ csplit tweets.yaml
> --prefix='tweet.' --suffix-format="%03d.yaml"
> --elide-empty-files
> /----/
10846
sh$ ls -l tweet.*
-rw-r--r-- 1 sylvain sylvain 10846 Jun 6 11:30 tweet.000.yaml
Bien: no más archivos vacíos. Pero en cierto sentido, el resultado es el peor ahora, desde entonces. csplit
divida el archivo en una sola pieza. Difícilmente podemos llamar a esto «dividir» un archivo, ¿verdad?
La explicación de este sorprendente resultado es csplit
no implica en absoluto que cada plato deba separarse sobre la base del mismo separador. De hecho, csplit
requiere que proporcione cualquier separador utilizado. Incluso si es el mismo varias veces:
sh$ csplit tweets.yaml
> --prefix='tweet.' --suffix-format="%03d.yaml"
> --elide-empty-files
> /----/ /----/ /----/
170
250
10426
Puse tres delimitadores (idénticos) en la línea de comando. Entonces, csplit
identifica el final de la primera pieza basándose en el primer separador. Esto da como resultado una pieza de cero bytes que se ha eliminado. La segunda pieza se distinguió de la siguiente línea. /----/
. Conduce a una pieza de 170 bytes. Finalmente, se identificó una tercera pieza con una longitud de 250 bytes basada en el tercer separador. Los datos restantes, 10 426 bytes, se colocaron en la última pieza.
sh$ ls -l tweet.???.yaml
-rw-r--r-- 1 sylvain sylvain 170 Jun 6 11:30 tweet.000.yaml
-rw-r--r-- 1 sylvain sylvain 250 Jun 6 11:30 tweet.001.yaml
-rw-r--r-- 1 sylvain sylvain 10426 Jun 6 11:30 tweet.002.yaml
Obviamente, no sería práctico si tuviéramos que proporcionar tantos separadores de línea de comando como partes del archivo de datos. Especialmente porque este número exacto generalmente no se conoce de antemano. Por suerte, csplit
hay un modelo especial que significa «repetir el modelo anterior tanto como sea posible». A pesar de la sintaxis que recuerda al cuantificador de estrellas en una expresión regular, está más cerca de Kleene plus concepto, ya que se utiliza para repetir un delimitador que ya se ha combinado una vez:
sh$ csplit tweets.yaml
> --prefix='tweet.' --suffix-format="%03d.yaml"
> --elide-empty-files
> /----/ '*'
170
250
190
208
140
[...]
247
285
194
214
185
131
316
221
Y esta vez finalmente dividí mi colección de tweets en partes separadas. Sin embargo, lo hace csplip
¿tienes otros modelos agradables «especiales» como este? Bueno, no sé si podemos llamarlos «especiales», pero definitivamente csplit
Entiendo más que modelos.
Más patrones para separar
En la sección anterior vimos cómo usar el cuantificador «*» para iteraciones no relacionadas. Sin embargo, al reemplazar la estrella por un número, puede solicitar el número exacto de repeticiones:
sh$ csplit tweets.yaml
> --prefix='tweet.' --suffix-format="%03d.yaml"
> --elide-empty-files
> /----/ '6'
170
250
190
208
140
216
9672
Esto conduce a un caso angular interesante. ¿Qué se agregará si el número de iteraciones excede el número de delimitadores reales en el archivo de datos? Bueno, veamos esto, por ejemplo:
sh$ csplit tweets.yaml
> --prefix='tweet.' --suffix-format="%03d.yaml"
> --elide-empty-files
> /----/ '999'
csplit: ‘/----/’: match not found on repetition 62
170
250
190
208
[...]
91
247
285
194
214
185
131
316
221
sh$ ls tweet.*
ls: cannot access 'tweet.*': No such file or directory
Curiosamente, no solo csplit
informó un error, pero también eliminó todos los archivos creados durante el proceso. Presta especial atención a mi redacción: remoto ellos. Esto significa que los archivos se crearon cuando csplit
encontró el error, los eliminó. En otras palabras, si ya tiene un archivo cuyo nombre parece un archivo de pieza, se eliminará:
sh$ touch tweet.002.yaml
sh$ csplit tweets.yaml
> --prefix='tweet.' --suffix-format="%03d.yaml"
> --elide-empty-files
> /----/ '999'
csplit: ‘/----/’: match not found on repetition 62
170
250
190
[...]
87
91
247
285
194
214
185
131
316
221
sh$ ls tweet.*
ls: cannot access 'tweet.*': No such file or directory
En el ejemplo anterior, tweet.002.yaml
el archivo que creamos manualmente fue reemplazado y luego eliminado de csplit
.
Puede cambiar este comportamiento usando --keep-files
opción. Como sugiere su nombre, no eliminará las piezas de csplit creadas después de un error:
sh$ csplit tweets.yaml
> --prefix='tweet.' --suffix-format="%03d.yaml"
> --elide-empty-files
> --keep-files
> /----/ '999'
csplit: ‘/----/’: match not found on repetition 62
170
250
190
[...]
316
221
sh$ ls tweet.*
tweet.000.yaml
tweet.001.yaml
tweet.002.yaml
tweet.003.yaml
[...]
tweet.058.yaml
tweet.059.yaml
tweet.060.yaml
tweet.061.yaml
Observe en este caso y a pesar del error, csplit
no rechazó ningún dato:
sh$ diff -s tweets.yaml <(cat tweet.*)
Files tweets.yaml and /dev/fd/63 are identical
Pero, ¿qué pasa si hay algunos datos en el archivo que quiero descartar? Okey, csplit
hay soporte limitado para este uso %regex%
modelo.
Omitir datos en csplit
Cuando se usa un signo de porcentaje (%
) como separador de la expresión regular en lugar de una barra (/
), csplit
omitirá los datos (pero no los incluirá) en la primera línea correspondiente a la expresión regular. Esto puede resultar útil para ignorar algunas entradas, especialmente al principio o al final del archivo de entrada:
sh$ # Keep only the first two tweets
sh$ csplit tweets.yaml
> --prefix='tweet.' --suffix-format="%03d.yaml"
> --elide-empty-files
> --keep-files
> /----/ '2' %----% '*'
170
250
sh$ head tweet.00[012].yaml
==> tweet.000.yaml <==
----
event:
repeat: days: 180
status: |
I think I use the `sed` command daily. And you?
https://www.yesik.it/EP07
#Shell #Linux #Sed #YesIKnowIT
==> tweet.001.yaml <==
----
status: |
Print the first column of a space-separated data file:
awk 'print $1' data.txt # Print out just the first column
For some unknown reason, I find that easier to remember than:
cut -f1 data.txt
#Linux #AWK #Cut
sh$ # Skip the first two tweets
sh$ csplit tweets.yaml
> --prefix='tweet.' --suffix-format="%03d.yaml"
> --elide-empty-files
> --keep-files
> %----% '2' /----/ '2'
190
208
140
9888
sh$ head tweet.00[012].yaml
==> tweet.000.yaml <==
----
status: |
For the #shell #beginners :
« #GlobPatterns : how to move hundreds of files in not time [1/3] »
#Unix #Linux
#YesIKnowIT
==> tweet.001.yaml <==
----
status: |
Want to know the oldest file in your disk?
find / -type f -printf '%TFT%.8TT %pn' | sort | less
(should work on any Single UNIX Specification compliant system)
#UNIX #Linux
==> tweet.002.yaml <==
----
status: |
When using the find command, use `-iname` instead of `-name` for case-insensitive search
#Unix #Linux #Shell #Find
sh$ # Keep only the third and fourth tweets
sh$ csplit tweets.yaml
> --prefix='tweet.' --suffix-format="%03d.yaml"
> --elide-empty-files
> --keep-files
> %----% '2' /----/ '2' %----% '*'
190
208
140
sh$ head tweet.00[012].yaml
==> tweet.000.yaml <==
----
status: |
For the #shell #beginners :
« #GlobPatterns : how to move hundreds of files in not time [1/3] »
#Unix #Linux
#YesIKnowIT
==> tweet.001.yaml <==
----
status: |
Want to know the oldest file in your disk?
find / -type f -printf '%TFT%.8TT %pn' | sort | less
(should work on any Single UNIX Specification compliant system)
#UNIX #Linux
==> tweet.002.yaml <==
----
status: |
When using the find command, use `-iname` instead of `-name` for case-insensitive search
#Unix #Linux #Shell #Find
Use compensaciones al dividir archivos con csplit
Cuando se utilizan expresiones regulares (o /…/
o %…%
) puede especificar positivo (+N
) o negativo (-N
) compensar al final del modelo para que csplit
dividirá el archivo N líneas antes o después de la línea correspondiente. Recuerda que en todos los casos el patrón determina el final de la pieza:
sh$ csplit tweets.yaml
> --prefix='tweet.' --suffix-format="%03d.yaml"
> --elide-empty-files
> --keep-files
> %----%+1 '2' /----/+1 '2' %----% '*'
190
208
140
sh$ head tweet.00[012].yaml
==> tweet.000.yaml <==
status: |
For the #shell #beginners :
« #GlobPatterns : how to move hundreds of files in not time [1/3] »
#Unix #Linux
#YesIKnowIT
----
==> tweet.001.yaml <==
status: |
Want to know the oldest file in your disk?
find / -type f -printf '%TFT%.8TT %pn' | sort | less
(should work on any Single UNIX Specification compliant system)
#UNIX #Linux
----
==> tweet.002.yaml <==
status: |
When using the find command, use `-iname` instead of `-name` for case-insensitive search
#Unix #Linux #Shell #Find
----
Dividido por el número de línea
Ya hemos visto cómo podemos usar una expresión regular para dividir archivos. En este caso, csplit
dividirá el archivo en la primera línea correspondiente a esta expresión regular. Pero también puedes identificar la línea divisoria por su número, como veremos ahora.
Antes de cambiar a YAML, solía almacenar mis tweets planificados en archivo plano.
Se ha realizado un tweet de dos líneas en este archivo. Uno contiene una iteración opcional y el otro contiene el texto del tweet, con nuevas líneas reemplazadas por n. Una vez más este archivo de muestra está disponible en línea.
También pudo utilizar este formato de «tamaño fijo» csplit
para poner cada tweet individual en su propio archivo:
sh$ csplit tweets.txt
> --prefix='tweet.' --suffix-format="%03d.txt"
> --elide-empty-files
> --keep-files
> 2 '*'
csplit: ‘2’: line number out of range on repetition 62
1
123
222
161
182
119
184
81
148
128
142
101
107
[...]
sh$ diff -s tweets.txt <(cat tweet.*.txt)
Files tweets.txt and /dev/fd/63 are identical
sh$ head tweet.00[012].txt
==> tweet.000.txt <==
==> tweet.001.txt <==
days:180
I think I use the `sed` command daily. And you?nnhttps://www.yesik.it/EP07n#Shell #Linux #Sedn#YesIKnowIT
==> tweet.002.txt <==
Print the first column of a space-separated data file:nawk 'print $1' data.txt # Print out just the first columnnnFor some unknown reason, I find that easier to remember than:ncut -f1 data.txtnn#Linux #AWK #Cut
El ejemplo anterior parece fácil de entender, pero aquí hay dos trampas. Primero, 2
dado como argumento para csplit
es un número de fila, no un número de fila. Sin embargo, cuando uso la repetición, como hice después del primer juego, csplit
usará este número como el número de filas. Si no está claro, le permito comparar el resultado de los siguientes tres comandos:
sh$ csplit tweets.txt --keep-files 2 2 2 2 2
csplit: warning: line number ‘2’ is the same as preceding line number
csplit: warning: line number ‘2’ is the same as preceding line number
csplit: warning: line number ‘2’ is the same as preceding line number
csplit: warning: line number ‘2’ is the same as preceding line number
1
0
0
0
0
9030
sh$ csplit tweets.txt --keep-files 2 4 6 8 10
1
123
222
161
182
8342
sh$ csplit tweets.txt --keep-files 2 '4'
1
123
222
161
182
8342
Mencioné una segunda trampa, algo relacionada con la primera. Es posible que haya notado la línea en blanco en la parte superior de tweets.txt
¿expediente? Esto lleva a esto tweet.000.txt
una pieza que contiene solo el carácter de nueva línea. Desafortunadamente, en este ejemplo se requirió debido a la repetición: recuerde que quiero partes de dos líneas. De modo que 2
se requiere antes de la repetición. Pero también significa que la primera pieza se romperá, pero no la segunda fila. En otras palabras, la primera pieza contiene una línea. Todos los demás contendrán 2 líneas. Quizás puedas compartir tu opinión en la sección de comentarios, pero creo que fue una elección de diseño desafortunada.
Puede aliviar este problema yendo directamente a la primera línea no vacía:
sh$ csplit tweets.txt
> --prefix='tweet.' --suffix-format="%03d.txt"
> --elide-empty-files
> --keep-files
> %.% 2 '*'
csplit: ‘2’: line number out of range on repetition 62
123
222
161
[...]
sh$ head tweet.00[012].txt
==> tweet.000.txt <==
days:180
I think I use the `sed` command daily. And you?nnhttps://www.yesik.it/EP07n#Shell #Linux #Sedn#YesIKnowIT
==> tweet.001.txt <==
Print the first column of a space-separated data file:nawk 'print $1' data.txt # Print out just the first columnnnFor some unknown reason, I find that easier to remember than:ncut -f1 data.txtnn#Linux #AWK #Cut
==> tweet.002.txt <==
For the #shell #beginners :n« #GlobPatterns : how to move hundreds of files in not time [1/3] »nhttps://youtu.be/TvW8DiEmTcQnn#Unix #Linuxn#YesIKnowIT
Leyendo de stdin
Por supuesto, como la mayoría de las herramientas de línea de comandos, csplit
puede leer la entrada de su entrada estándar. En este caso, debe especificar -
como nombre de archivo de entrada:
sh$ tr [:lower:] [:upper:] < tweets.txt | csplit -
> --prefix='tweet.' --suffix-format="%03d.txt"
> --elide-empty-files
> --keep-files
> %.% 2 '3'
123
222
161
8524
sh$ head tweet.???.txt
==> tweet.000.txt <==
DAYS:180
I THINK I USE THE `SED` COMMAND DAILY. AND YOU?NNHTTPS://WWW.YESIK.IT/EP07N#SHELL #LINUX #SEDN#YESIKNOWIT
==> tweet.001.txt <==
PRINT THE FIRST COLUMN OF A SPACE-SEPARATED DATA FILE:NAWK 'PRINT $1' DATA.TXT # PRINT OUT JUST THE FIRST COLUMNNNFOR SOME UNKNOWN REASON, I FIND THAT EASIER TO REMEMBER THAN:NCUT -F1 DATA.TXTNN#LINUX #AWK #CUT
==> tweet.002.txt <==
FOR THE #SHELL #BEGINNERS :N« #GLOBPATTERNS : HOW TO MOVE HUNDREDS OF FILES IN NOT TIME [1/3] »NHTTPS://YOUTU.BE/TVW8DIEMTCQNN#UNIX #LINUXN#YESIKNOWIT
==> tweet.003.txt <==
WANT TO KNOW THE OLDEST FILE IN YOUR DISK?NNFIND / -TYPE F -PRINTF '%TFT%.8TT %PN' | SORT | LESSN(SHOULD WORK ON ANY SINGLE UNIX SPECIFICATION COMPLIANT SYSTEM)N#UNIX #LINUX
WHEN USING THE FIND COMMAND, USE `-INAME` INSTEAD OF `-NAME` FOR CASE-INSENSITIVE SEARCHN#UNIX #LINUX #SHELL #FIND
FROM A POSIX SHELL `$OLDPWD` HOLDS THE NAME OF THE PREVIOUS WORKING DIRECTORY:NCD /TMPNECHO YOU ARE HERE: $PWDNECHO YOU WERE HERE: $OLDPWDNCD $OLDPWDNN#UNIX #LINUX #SHELL #CD
FROM A POSIX SHELL, "CD" IS A SHORTHAND FOR CD $HOMEN#UNIX #LINUX #SHELL #CD
HOW TO MOVE HUNDREDS OF FILES IN NO TIME?NUSING THE FIND COMMAND!NNHTTPS://YOUTU.BE/ZMEFXJYZAQKN#UNIX #LINUX #MOVE #FILES #FINDN#YESIKNOWIT
Y eso es casi todo lo que quería mostrarte hoy. Espero usar csplit para dividir archivos en Linux en el futuro. ¡Si te gustó este artículo y no olvides compartirlo y dar me gusta en tu red social favorita!
El Blog lo hacemos para colaborar y servir de referencia a la gente Linux. Esperamos que os guste.