sustituir - sed: reemplazar espacios entre comillas con guiones bajos
sed linux (5)
Para una solución sed
only (que no necesariamente defiendo), intente:
echo ''a b "c d e" f g "h i"'' |/
sed '':a;s/^/(/([^"]*"[^"]*"[^"]*/)*[^"]*"[^"]*/) //1_/;ta''
a b "c_d_e" f g "h_i"
Traducción:
- Comience al comienzo de la línea.
- Busque el patrón
junk"junk"
, repetido cero o más veces, donde lajunk
no tiene una cita, seguido dejunk"junk space
. - Reemplace el espacio final con
_
. - Si tiene éxito, regrese al principio.
Tengo entrada (por ejemplo, desde ifconfig run0 scan
en OpenBSD) que tiene algunos campos separados por espacios, pero algunos de los campos contienen espacios (afortunadamente, los campos que contienen espacios están siempre entre comillas).
Necesito distinguir entre los espacios dentro de las comillas y los espacios separadores. La idea es reemplazar espacios entre comillas con guiones bajos.
Data de muestra:
%cat /tmp/ifconfig_scan | fgrep nwid | cut -f3
nwid Websense chan 6 bssid 00:22:7f:xx:xx:xx 59dB 54M short_preamble,short_slottime
nwid ZyXEL chan 8 bssid cc:5d:4e:xx:xx:xx 5dB 54M privacy,short_slottime
nwid "myTouch 4G Hotspot" chan 11 bssid d8:b3:77:xx:xx:xx 49dB 54M privacy,short_slottime
Lo cual no termina procesado de la manera que quiero, ya que aún no he reemplazado los espacios entre las comillas con los guiones bajos:
%cat /tmp/ifconfig_scan | fgrep nwid | cut -f3 |/
cut -s -d '' '' -f 2,4,6,7,8 | sort -n -k4
"myTouch Hotspot" 11 bssid d8:b3:77:xx:xx:xx
ZyXEL 8 cc:5d:4e:xx:xx:xx 5dB 54M
Websense 6 00:22:7f:xx:xx:xx 59dB 54M
NO ES UNA RESPUESTA, simplemente publicando un código equivalente a awk para el código perl de @ steve en caso de que alguien esté interesado (y para ayudarme a recordar esto en el futuro):
@steve publicado:
perl -pe ''s:"[^/"]*":($x=$&)=~s/ /_/g;$x:ge''
y al leer la explicación de @ steve, el awk más breve equivalente a ese código perl (NO la solución awk preferida - vea la respuesta de @ Kent para eso) sería el awk de GNU:
gawk ''{
head = ""
while ( match($0,"/"[^/"]*/"") ) {
head = head substr($0,1,RSTART-1) gensub(/ /,"_","g",substr($0,RSTART,RLENGTH))
$0 = substr($0,RSTART+RLENGTH)
}
print head $0
}''
lo cual llegamos a partir de una solución POSIX awk con más variables:
awk ''{
head = ""
tail = $0
while ( match(tail,"/"[^/"]*/"") ) {
x = substr(tail,RSTART,RLENGTH)
gsub(/ /,"_",x)
head = head substr(tail,1,RSTART-1) x
tail = substr(tail,RSTART+RLENGTH)
}
print head tail
}''
y guardando una línea con GNU awk''s gensub ():
gawk ''{
head = ""
tail = $0
while ( match(tail,"/"[^/"]*/"") ) {
x = gensub(/ /,"_","g",substr(tail,RSTART,RLENGTH))
head = head substr(tail,1,RSTART-1) x
tail = substr(tail,RSTART+RLENGTH)
}
print head tail
}''
y luego deshacerse de la variable x:
gawk ''{
head = ""
tail = $0
while ( match(tail,"/"[^/"]*/"") ) {
head = head substr(tail,1,RSTART-1) gensub(/ /,"_","g",substr(tail,RSTART,RLENGTH))
tail = substr(tail,RSTART+RLENGTH)
}
print head tail
}''
y luego deshacerte de la variable "cola" si no necesitas $ 0, NF, etc., que quedaron después del ciclo:
gawk ''{
head = ""
while ( match($0,"/"[^/"]*/"") ) {
head = head substr($0,1,RSTART-1) gensub(/ /,"_","g",substr($0,RSTART,RLENGTH))
$0 = substr($0,RSTART+RLENGTH)
}
print head $0
}''
prueba esto:
awk -F''"'' ''{for(i=2;i<=NF;i++)if(i%2==0)gsub(" ","_",$i);}1'' OFS="/"" file
Funciona para partes de varias citas en una línea:
echo ''"first part" foo "2nd part" bar "the 3rd part comes" baz''| awk -F''"'' ''{for(i=2;i<=NF;i++)if(i%2==0)gsub(" ","_",$i);}1'' OFS="/""
"first_part" foo "2nd_part" bar "the_3rd_part_comes" baz
EDITAR formulario alternativo:
awk ''BEGIN{FS=OFS="/""} {for(i=2;i<NF;i+=2)gsub(" ","_",$i)} 1'' file
Estarías mejor con perl
. El código es mucho más legible y mantenible:
perl -pe ''s:"[^"]*":($x=$&)=~s/ /_/g;$x:ge''
Con su aporte, los resultados son:
a b "c_d_e" f g "h_i"
Explicación:
-p # enable printing
-e # the following expression...
s # begin a substitution
: # the first substitution delimiter
"[^"]*" # match a double quote followed by anything not a double quote any
# number of times followed by a double quote
: # the second substitution delimiter
($x=$&)=~s/ /_/g; # copy the pattern match ($&) into a variable ($x), then
# substitute a space for an underscore globally on $x. The
# variable $x is needed because capture groups and
# patterns are read only variables.
$x # return $x as the replacement.
: # the last delimiter
g # perform the nested substitution globally
e # make sure that the replacement is handled as an expression
Algunas pruebas:
for i in {1..500000}; do echo ''a b "c d e" f g "h i" j k l "m n o "p q r" s t" u v "w x" y z'' >> test; done
time perl -pe ''s:"[^"]*":($x=$&)=~s/ /_/g;$x:ge'' test >/dev/null
real 0m8.301s
user 0m8.273s
sys 0m0.020s
time awk ''BEGIN{FS=OFS="/""} {for(i=2;i<NF;i+=2)gsub(" ","_",$i)} 1'' test >/dev/null
real 0m4.967s
user 0m4.924s
sys 0m0.036s
time awk ''!(NR%2){gsub(FS,"_")}1'' RS=/" ORS=/" test >/dev/null
real 0m4.336s
user 0m4.244s
sys 0m0.056s
time sed '':a;s/^/(/([^"]*"[^"]*"[^"]*/)*[^"]*"[^"]*/) //1_/;ta'' test >/dev/null
real 2m26.101s
user 2m25.925s
sys 0m0.100s
Otro awk para intentar:
awk ''!(NR%2){gsub(FS,"_")}1'' RS=/" ORS=/"
Eliminando las comillas
awk ''!(NR%2){gsub(FS,"_")}1'' RS=/" ORS=
Algunas pruebas adicionales con un archivo de prueba de tamaño triple además de las pruebas anteriores realizadas por @steve. Tuve que transformar la declaración de sed
un poco para que los sed
no-GNU pudieran procesarla también. bwk
awk
( bwk
) gawk3
, gawk4
y mawk
:
$ for i in {1..1500000}; do echo ''a b "c d e" f g "h i" j k l "m n o "p q r" s t" u v "w x" y z'' ; done > test
$ time perl -pe ''s:"[^"]*":($x=$&)=~s/ /_/g;$x:ge'' test >/dev/null
real 0m27.802s
user 0m27.588s
sys 0m0.177s
$ time awk ''BEGIN{FS=OFS="/""} {for(i=2;i<NF;i+=2)gsub(" ","_",$i)} 1'' test >/dev/null
real 0m6.565s
user 0m6.500s
sys 0m0.059s
$ time gawk3 ''BEGIN{FS=OFS="/""} {for(i=2;i<NF;i+=2)gsub(" ","_",$i)} 1'' test >/dev/null
real 0m21.486s
user 0m18.326s
sys 0m2.658s
$ time gawk4 ''BEGIN{FS=OFS="/""} {for(i=2;i<NF;i+=2)gsub(" ","_",$i)} 1'' test >/dev/null
real 0m14.270s
user 0m14.173s
sys 0m0.083s
$ time mawk ''BEGIN{FS=OFS="/""} {for(i=2;i<NF;i+=2)gsub(" ","_",$i)} 1'' test >/dev/null
real 0m4.251s
user 0m4.193s
sys 0m0.053s
$ time awk ''!(NR%2){gsub(FS,"_")}1'' RS=/" ORS=/" test >/dev/null
real 0m13.229s
user 0m13.141s
sys 0m0.075s
$ time gawk3 ''!(NR%2){gsub(FS,"_")}1'' RS=/" ORS=/" test >/dev/null
real 0m33.965s
user 0m26.822s
sys 0m7.108s
$ time gawk4 ''!(NR%2){gsub(FS,"_")}1'' RS=/" ORS=/" test >/dev/null
real 0m15.437s
user 0m15.328s
sys 0m0.087s
$ time mawk ''!(NR%2){gsub(FS,"_")}1'' RS=/" ORS=/" test >/dev/null
real 0m4.002s
user 0m3.948s
sys 0m0.051s
$ time sed -e :a -e ''s/^/(/([^"]*"[^"]*"[^"]*/)*[^"]*"[^"]*/) //1_/;ta'' test > /dev/null
real 5m14.008s
user 5m13.082s
sys 0m0.580s
$ time gsed -e :a -e ''s/^/(/([^"]*"[^"]*"[^"]*/)*[^"]*"[^"]*/) //1_/;ta'' test > /dev/null
real 4m11.026s
user 4m10.318s
sys 0m0.463s
mawk
rindió los resultados más rápidos ...