regex - probar - metacaracteres expresiones regulares
¿Es posible escapar metacaracteres regex de manera confiable con sed (2)
Nota:
-
Si está buscando una
funcionalidad preempaquetada
basada en las técnicas discutidas en esta respuesta:
-
bash
funciones debash
que permiten un escape robusto incluso en sustituciones de varias líneas se pueden encontrar en la parte inferior de esta publicación (más una soluciónperl
que utiliza el soporte incorporado deperl
para dicho escape). -
La respuesta de @ EdMorton
contiene una
herramienta
(script
bash
) que realiza sustituciones de una sola línea de manera robusta.
-
-
Todos los fragmentos suponen
bash
como shell (son posibles reformulaciones compatibles con POSIX):
Soluciones de una línea
Escapar de un literal de cadena para usar como una
expresión regular
en
sed
:
Para dar crédito donde se debe el crédito: encontré la expresión regular utilizada a continuación en esta respuesta .
Suponiendo que la cadena de búsqueda es una cadena de una sola línea :
search=''abc/n/t[a-z]/+/([^ ]/)/{2,3/}/3'' # sample input containing metachars.
searchEscaped=$(sed ''s/[^^]/[&]/g; s//^///^/g'' <<<"$search") # escape it.
sed -n "s/$searchEscaped/foo/p" <<<"$search" # if ok, echoes ''foo''
-
Cada carácter, excepto
^
se coloca en su propia expresión[...]
juego de caracteres para tratarlo como un literal.-
Tenga en cuenta que
^
es el único carácter. no puede representar como[^]
, porque tiene un significado especial en esa ubicación (negación).
-
Tenga en cuenta que
-
Entonces,
^
caracteres. se escapan como/^
.-
Tenga en cuenta que no puede escapar de cada carácter poniendo un
/
delante de él porque eso puede convertir un carácter literal en un metacargador, por ejemplo,/<
y/b
son límites de palabras en algunas herramientas,/n
es una nueva línea,/{
es el inicio de un intervalo RE como/{1,3/}
, etc.
-
Tenga en cuenta que no puede escapar de cada carácter poniendo un
El enfoque es robusto, pero no eficiente.
La robustez proviene de no tratar de anticipar todos los caracteres especiales de expresiones regulares , que variarán entre los dialectos de expresiones regulares, sino de enfocarse en solo 2 características compartidas por todos los dialectos de expresiones regulares :
- la capacidad de especificar caracteres literales dentro de un conjunto de caracteres.
-
la capacidad de escapar de un
^
as/^
literal
Escapar de un literal de cadena para usar como la
cadena de reemplazo
en el comando
s///
sed
:
La cadena de reemplazo en un comando
sed
s///
no es una expresión regular, pero reconoce
marcadores de posición
que se refieren a la cadena completa que coincide con la expresión regular (
&
) o los resultados específicos del grupo de captura por índice (
/1
,
/2
..), por lo que se deben escapar, junto con el delimitador de expresiones regulares (habitual),
/
.
Suponiendo que la cadena de reemplazo es una cadena de una sola línea :
replace=''Laurel & Hardy; PS/2'' # sample input containing metachars.
replaceEscaped=$(sed ''s/[&//]///&/g'' <<<"$replace") # escape it
sed -n "s//(.*/) /(.*/)/$replaceEscaped/p" <<<"foo bar" # if ok, outputs $replace as is
Soluciones MULTI-line
Escapar de un literal de cadena MULTI-LINE para usar como una
expresión regular
en
sed
:
Nota
: Esto solo tiene sentido si se han leído
varias líneas de entrada
(posiblemente TODAS) antes de intentar hacer coincidir.
Dado que las herramientas como
sed
y
awk
funcionan en una
sola
línea a la vez de forma predeterminada, se necesitan pasos adicionales para que lean más de una línea a la vez.
# Define sample multi-line literal.
search=''/abc/n/t[a-z]/+/([^ ]/)/{2,3/}/3
/def/n/t[A-Z]/+/([^ ]/)/{3,4/}/4''
# Escape it.
searchEscaped=$(sed -e ''s/[^^]/[&]/g; s//^///^/g; $!a/'$''/n''''//n'' <<<"$search" | tr -d ''/n'') #''
# Use in a Sed command that reads ALL input lines up front.
# If ok, echoes ''foo''
sed -n -e '':a'' -e ''$!{N;ba'' -e ''}'' -e "s/$searchEscaped/foo/p" <<<"$search"
-
Las nuevas líneas en cadenas de entrada de varias líneas deben traducirse a cadenas
''/n''
, que es cómo se codifican las nuevas líneas en una expresión regular. -
$!a/'$''/n''''//n''
agrega la cadena''/n''
a cada línea de salida pero a la última (la última línea nueva se ignora, porque fue agregada por<<<
) -
tr -d ''/n
luego elimina todas las nuevas líneas reales de la cadena (sed
agrega una cada vez que imprime su espacio de patrón), reemplazando efectivamente todas las nuevas líneas en la entrada con cadenas''/n''
.
-
-e '':a'' -e ''$!{N;ba'' -e ''}''
es la forma compatible con POSIX de un idioma desed
que lee todas las líneas de entrada en un bucle, por lo que deja que los comandos subsiguientes funcionen en todas las líneas de entrada en una vez.-
Si está utilizando
GNU
sed
(solo), puede usar su opción-z
para simplificar la lectura de todas las líneas de entrada a la vez:
sed -z "s/$searchEscaped/foo/" <<<"$search"
-
Si está utilizando
GNU
Escapar de un literal de cadena MULTI-LINE para usar como la
cadena de reemplazo
en el comando
s///
sed
:
# Define sample multi-line literal.
replace=''Laurel & Hardy; PS/2
Masters/1 & Johnson/2''
# Escape it for use as a Sed replacement string.
IFS= read -d '''' -r < <(sed -e '':a'' -e ''$!{N;ba'' -e ''}'' -e ''s/[&//]///&/g; s//n///&/g'' <<<"$replace")
replaceEscaped=${REPLY%$''/n''}
# If ok, outputs $replace as is.
sed -n "s//(.*/) /(.*/)/$replaceEscaped/p" <<<"foo bar"
-
Las nuevas líneas en la cadena de entrada deben conservarse como nuevas líneas reales, pero
/
-escaped. -
-e '':a'' -e ''$!{N;ba'' -e ''}''
es la forma compatible con POSIX de un idioma desed
que lee todas las líneas de entrada en un bucle. -
''s/[&//]///&/g
escapa a todas las instancias&
,/
y/
, como en la solución de línea única. -
s//n///&/g''
entonces/
-prefija todas las nuevas líneas reales. -
IFS= read -d '''' -r
se usa para leer la salida del comandosed
tal como está (para evitar la eliminación automática de las nuevas líneas finales que realizaría una sustitución de comando ($(...)
)). -
${REPLY%$''/n''}
luego elimina una nueva línea final, que<<<
ha agregado implícitamente a la entrada.
funciones
bash
basadas en lo anterior (para
sed
):
-
quoteRe()
comillas (escapes) para usar en una expresión regular -
quoteSubst()
cita para usar en la cadena de sustitución de una llamadas///
. -
ambos manejan la entrada
multilínea
correctamente
-
Tenga en cuenta que debido a que
sed
lee una sola línea a la vez de forma predeterminada, el uso dequoteRe()
con cadenas de varias líneas solo tiene sentido en los comandossed
que leen explícitamente varias (o todas) líneas a la vez. -
Además, el uso de sustituciones de comandos (
$(...)
) para llamar a las funciones no funcionará para las cadenas que tienen líneas nuevas al final ; en ese caso, use algo comoIFS= read -d '''' -r escapedValue <(quoteSubst "$value")
-
Tenga en cuenta que debido a que
# SYNOPSIS
# quoteRe <text>
quoteRe() { sed -e ''s/[^^]/[&]/g; s//^///^/g; $!a/'$''/n''''//n'' <<<"$1" | tr -d ''/n''; }
# SYNOPSIS
# quoteSubst <text>
quoteSubst() {
IFS= read -d '''' -r < <(sed -e '':a'' -e ''$!{N;ba'' -e ''}'' -e ''s/[&//]///&/g; s//n///&/g'' <<<"$1")
printf %s "${REPLY%$''/n''}"
}
Ejemplo:
from=$''Cost/(*):/n$3.'' # sample input containing metachars.
to=''You & I''$''/n''''eating A/1 sauce.'' # sample replacement string with metachars.
# Should print the unmodified value of $to
sed -e '':a'' -e ''$!{N;ba'' -e ''}'' -e "s/$(quoteRe "$from")/$(quoteSubst "$to")/" <<<"$from"
Tenga en cuenta el uso de
-e '':a'' -e ''$!{N;ba'' -e ''}''
para leer todas las entradas a la vez, de modo que la sustitución de varias líneas funcione.
solución
perl
:
Perl tiene soporte incorporado
para escapar de cadenas arbitrarias para uso literal en una expresión regular: la función
quotemeta()
o su equivalente
/Q.../E
citando
.
El enfoque es el mismo para cadenas de una o varias líneas;
por ejemplo:
from=$''Cost/(*):/n$3.'' # sample input containing metachars.
to=''You owe me $1/$& for''$''/n''''eating A/1 sauce.'' # sample replacement string w/ metachars.
# Should print the unmodified value of $to.
# Note that the replacement value needs NO escaping.
perl -s -0777 -pe ''s//Q$from/E/$to/'' -- -from="$from" -to="$to" <<<"$from"
-
Tenga en cuenta el uso de
-0777
para leer todas las entradas a la vez, de modo que la sustitución de varias líneas funcione. -
La opción
-s
permite colocar-<var>=<val>
-style definiciones de variables de Perl a continuación--
después del script, antes de cualquier operando de nombre de archivo.
Me pregunto si es posible escribir un comando
sed
100% confiable para escapar de los metacaracteres regex en una cadena de entrada para que pueda usarse en un comando sed posterior.
Me gusta esto:
#!/bin/bash
# Trying to replace one regex by another in an input file with sed
search="/abc/n/t[a-z]/+/([^ ]/)/{2,3/}/3"
replace="/xyz/n/t[0-9]/+/([^ ]/)/{2,3/}/3"
# Sanitize input
search=$(sed ''script to escape'' <<< "$search")
replace=$(sed ''script to escape'' <<< "$replace")
# Use it in a sed command
sed "s/$search/$replace/" input
Sé que hay mejores herramientas para trabajar con cadenas fijas en lugar de patrones, por ejemplo
awk
,
perl
o
python
.
Solo me gustaría probar si es posible o no con
sed
.
¡Diría que concentrémonos en expresiones regulares POSIX para divertirnos aún más!
:)
He intentado muchas cosas, pero en cualquier momento pude encontrar una entrada que rompió mi intento.
Pensé que mantenerlo abstracto como
script to escape
no llevaría a nadie a la dirección equivocada.
Por cierto, la discusión surgió here . Pensé que este podría ser un buen lugar para recolectar soluciones y probablemente romperlas y / o elaborarlas.
Sobre la base de
la respuesta de @ mklement0
en este hilo, la siguiente herramienta reemplazará cualquier cadena de una sola línea (en lugar de regexp) con cualquier otra cadena de una sola línea usando
sed
y
bash
:
$ cat sedstr
#!/bin/bash
old="$1"
new="$2"
file="${3:--}"
escOld=$(sed ''s/[^^]/[&]/g; s//^///^/g'' <<< "$old")
escNew=$(sed ''s/[&//]///&/g'' <<< "$new")
sed "s/$escOld/$escNew/g" "$file"
Para ilustrar la necesidad de esta herramienta, considere intentar reemplazar
a.*/b{2,}/nc
con
d&e/1f
llamando a
sed
directamente:
$ cat file
a.*/b{2,}/nc
axx/bb/nc
$ sed ''s/a.*/b{2,}/nc/d&e/1f/'' file
sed: -e expression #1, char 16: unknown option to `s''
$ sed ''s/a.*//b{2,}/nc/d&e/1f/'' file
sed: -e expression #1, char 23: invalid reference /1 on `s'' command''s RHS
$ sed ''s/a.*//b{2,}/nc/d&e//1f/'' file
a.*/b{2,}/nc
axx/bb/nc
# .... and so on, peeling the onion ad nauseum until:
$ sed ''s/a/./*//b{2,}//nc/d/&e//1f/'' file
d&e/1f
axx/bb/nc
o use la herramienta anterior:
$ sedstr ''a.*/b{2,}/nc'' ''d&e/1f'' file
d&e/1f
axx/bb/nc
La razón por la que esto es útil es que se puede aumentar fácilmente para usar delimitadores de palabras para reemplazar palabras si es necesario, por ejemplo, en la sintaxis de
sed
GNU
sed "s//<$escOld/>/$escNew/g" "$file"
mientras que las herramientas que realmente funcionan en cadenas (por ejemplo, el
index()
awk
index()
) no pueden usar delimitadores de palabras.