%20-%20para%20m%C3%BAltiples%20archivos%20en%20BaSH.png)
Advertencia: principiante absoluto. Necesito agregar una columna a un archivo .csv donde el encabezado de la columna pueda ser "Nombre", pero toda la columna debe ser exactamente igual: el nombre del archivo en sí, el archivo filename
. Cada archivo tiene ahora sólo 3 variables, pero 2100 filas.
Ejemplo: para archivo"bcc1_45Fall_10010002.csv"Esto es lo que tengo -
HUC8 YEAR RO_MM
10010002 1961 74.7
10010002 1962 69.1
10010002 1963 52.0
10010002 1964 130.7
10010002 1965 32.2
10010002 1966 85.4
Esto es lo que quiero -
NAME HUC8 YEAR RO_MM
bcc1_45Fall_10010002 10010002 1961 74.7
bcc1_45Fall_10010002 10010002 1962 69.1
bcc1_45Fall_10010002 10010002 1963 52.0
bcc1_45Fall_10010002 10010002 1964 130.7
bcc1_45Fall_10010002 10010002 1965 32.2
bcc1_45Fall_10010002 10010002 1966 85.4
O esto -
HUC8 YEAR RO_MM
bcc1_45Fall_10010002 1961 74.7
bcc1_45Fall_10010002 1962 69.1
bcc1_45Fall_10010002 1963 52.0
bcc1_45Fall_10010002 1964 130.7
bcc1_45Fall_10010002 1965 32.2
bcc1_45Fall_10010002 1966 85.4
Si pudiera simplemente reemplazar todos los datos en la columna "HUC8" con eso, filename
sería perfecto. No es necesario que sea una columna adicional.
Necesito hacer esto para muchos miles de archivos.
Si supiera cómo hacer la primera parte, podría crear un bucle. ¿Pero tal vez haya incluso una manera mejor?
No se donde empezar.
Respuesta1
Usando awk
y column
:
$ awk '
NR==1{ sub(/\.csv$/, "", FILENAME) } # remove .csv suffix from FILENAME
NR>1{ $1=FILENAME } # replace the first field with filename
1 # print record
' bcc1_45Fall_10010002.csv | column -t
HUC8 YEAR RO_MM
bcc1_45Fall_10010002 1961 74.7
bcc1_45Fall_10010002 1962 69.1
bcc1_45Fall_10010002 1963 52.0
bcc1_45Fall_10010002 1964 130.7
bcc1_45Fall_10010002 1965 32.2
bcc1_45Fall_10010002 1966 85.4
Puede ejecutar esto en un bucle de shell para guardar los archivos modificados en el directorio modified_files
:
mkdir modified_files &&
for i in *.csv; do
awk 'NR==1{ sub(/\.csv$/, "", FILENAME) } NR>1{ $1=FILENAME }1' "$i" |
column -t > "./modified_files/$i"
done
Si necesita reemplazar la columna HUC8
y esta no es la primera columna, cambie el código a este:
awk -v search='HUC8' '
NR==1{
for(i=1;i<=NF;i++)
if ($i==search){ fld=i; sub(/\.csv$/, "", FILENAME); break }
}
NR>1{ $fld=FILENAME }
1
' file.csv | column -t
Respuesta2
UsandoMolinero, y suponiendo que sus archivos sean CSV "simples" (sin comasdentrocampos, etc. (puede cambiar --csvlite
a --csv
si se requiere compatibilidad total con RFC-4180)
$ cat bcc1_45Fall_10010002.csv
HUC8,YEAR,RO_MM
10010002,1961,74.7
10010002,1962,69.1
10010002,1963,52.0
10010002,1964,130.7
10010002,1965,32.2
10010002,1966,85.4
entonces
para reemplazar la
HUC8
columna actual:$ mlr --csvlite put -S '$HUC8 = substr(FILENAME,0,-5)' bcc1_45Fall_10010002.csv HUC8,YEAR,RO_MM bcc1_45Fall_10010002,1961,74.7 bcc1_45Fall_10010002,1962,69.1 bcc1_45Fall_10010002,1963,52.0 bcc1_45Fall_10010002,1964,130.7 bcc1_45Fall_10010002,1965,32.2 bcc1_45Fall_10010002,1966,85.4
para agregar una
Name
columna separada:$ mlr --csvlite put -S '$Name = substr(FILENAME,0,-5)' bcc1_45Fall_10010002.csv HUC8,YEAR,RO_MM,Name 10010002,1961,74.7,bcc1_45Fall_10010002 10010002,1962,69.1,bcc1_45Fall_10010002 10010002,1963,52.0,bcc1_45Fall_10010002 10010002,1964,130.7,bcc1_45Fall_10010002 10010002,1965,32.2,bcc1_45Fall_10010002 10010002,1966,85.4,bcc1_45Fall_10010002
para agregar una
Name
columna como primera columna:$ mlr --csvlite put -S '$Name = substr(FILENAME,0,-5)' then reorder -f Name bcc1_45Fall_10010002.csv Name,HUC8,YEAR,RO_MM bcc1_45Fall_10010002,10010002,1961,74.7 bcc1_45Fall_10010002,10010002,1962,69.1 bcc1_45Fall_10010002,10010002,1963,52.0 bcc1_45Fall_10010002,10010002,1964,130.7 bcc1_45Fall_10010002,10010002,1965,32.2 bcc1_45Fall_10010002,10010002,1966,85.4
Todo lo anterior escribe el resultado en la salida estándar; para modificar el archivo en el lugar, agregue la -I
opción. Puede pasar varios archivos a la vez usando Shell globs ex. bcc*.csv
o *.csv
.
[Al probarsin -I
la línea del encabezado no se repetirá a menos que se requiera un nuevo encabezado como resultado de la heterogeneidad del registro; sin embargo, -I
se agregará un encabezado apropiado a cada archivo.]
Respuesta3
$ perl -lne 'BEGIN {$fnr=1};
if ($fnr == 1) {
($fn = $ARGV) =~ s/\.[^.]+$//;
print "NAME,$_"
} else {
print "$fn,$_"
};
$fnr++;
if (eof) {$fnr=1}' *.csv
Esto agregará el nombre del archivo (sin la "extensión" .csv) como primer campo e imprimirá el contenido de los archivos .csv en la salida estándar.
A diferencia de awk
, perl
no realiza un seguimiento del recuento de líneas de cada archivo individual (sólo realiza un seguimiento del recuento total de líneas, con la $.
variable). Este script mantiene ese recuento manualmente, primero configurando la variable $fnr
en el bloque BEGIN, luego incrementándola para cada línea leída y finalmente restableciéndola a 1 cada vez que se llega al final de un archivo.
Esto se modifica fácilmente para agregar el nombre del archivo como último campo en lugar del primero. por ejemplo, cambie las dos print
declaraciones a:
print "$_,NAME"
and:
print "$_,$fn"
Si necesita insertar el campo de nombre de archivo en algún otro lugar de la línea, en lugar de como el primer campo, puede usar splice
la función de Perl.
Por ejemplo, lo siguiente inserta el nombre del archivo como tercer campo (tenga en cuenta que los índices de la matriz Perl comienzan desde cero, no desde 1, por lo que el tercer campo es $F[2]
, no $F[3]
):
$ perl -F, -lne 'BEGIN {$fnr=1; $field_num=2};
if ($fnr == 1) {
($fn = $ARGV) =~ s/\.[^.]+$//;
splice @F, $field_num, 0, "NAME";
} else {
splice @F, $field_num, 0, $fn;
};
print join(",", @F);
$fnr++;
if (eof) {$fnr=1}' *.csv
Esto utiliza la opción de Perl -F
para establecer una coma como delimitador de campo. Esto también permite que la función de división automática de Perl divida automáticamente la línea de entrada en una matriz llamada @F
(esto es similar al comportamiento predeterminado de awk de dividir automáticamente la línea de entrada en $1, $2, $3, etc.). La cadena literal "NOMBRE" o el nombre de archivo modificado se empalma en @F, luego los elementos de la @F
matriz se imprimen, unidos con caracteres de coma.
Finalmente, si realmente desea cambiar el contenido de los archivos, use -i
la opción de Perl. Opcionalmente, puede mantener una copia de seguridad del archivo original utilizando una "extensión" con la -i
opción, por ejemplo, cambiar el nombre filename.csv
a . Por ejemplo:filename.csv.orig
-iorig
perl -iorig -lne '......' *.csv
o
perl -iorig -F, -lne '......' *.csv
Respuesta4
Luego recorra los nombres de archivos e imprima columnas con awk
for f in *.csv;
do
head -1 $f > out/$f
cat $f | awk -v FIN=${f%.csv} 'NR>1 {print FIN, $2, $3}' >> out/$f
done
HUC8 YEAR RO_MM
bcc1_45Fall_10010002 1961 74.7
(...)