solo regulares regular probar numeros metacaracteres letras expresiones expresion espacios espacio ejemplos blanco alfanumerico regex sed

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 de bash 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ón perl que utiliza el soporte incorporado de perl 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).
  • 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.

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 de sed 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"

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 de sed 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 comando sed 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 llamada s/// .
  • 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 de quoteRe() con cadenas de varias líneas solo tiene sentido en los comandos sed 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 como IFS= read -d '''' -r escapedValue <(quoteSubst "$value")

# 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.