Sustitución en archivo de texto ** sin ** expresiones regulares


68

Necesito sustituir algo de texto dentro de un archivo de texto con un reemplazo. Por lo general, haría algo como

sed -i 's/text/replacement/g' path/to/the/file

El problema es que tanto texty replacementson cadenas complejas que contienen guiones, barras, blackslashes, citas y así sucesivamente. Si escapo, todos los personajes necesarios dentro de textla cosa se vuelven rápidamente ilegibles. Por otro lado, no necesito el poder de las expresiones regulares: solo necesito sustituir el texto literalmente.

¿Hay alguna forma de hacer una sustitución de texto sin usar expresiones regulares con algún comando bash?

Sería bastante trivial escribir un guión que haga esto, pero creo que ya debería existir algo.


¿Necesario hacerlo a través de bash? Una solución simplista sería abrir en Word y hacer unfind and replace all
Akash

17
@akash ¿Porque los sistemas que bashsiempre se envían con Microsoft Word? ;) No, solo bromeo. Sin embargo, el OP puede querer hacer esto en una máquina remota o para un lote de archivos.
slhck

@slhck :) Bueno, supongo que gedit debería tener una opción similar
Akash,

Una opción sería escapar de alguna manera correctamente antes de pasarlo sed, lo que probablemente sea un esfuerzo inútil considerando todos los interruptores y las diferencias de plataforma.
l0b0

Respuestas:


6

Cuando no necesite el poder de las expresiones regulares, no lo use. Eso está bien.
Pero, esto no es realmente una expresión regular .

sed 's|literal_pattern|replacement_string|g'

Entonces, si /es su problema, use |y no necesita escapar del primero.

ps: sobre los comentarios, también vea esta respuesta de Stackoverflow en Escape a string for sed search pattern .


Actualización: si estás bien con Perl, pruébalo \Qy de \Eesta manera,
perl -pe 's|\Qliteral_pattern\E|replacement_string|g'
RedGrittyBricktambién ha sugerido un truco similar con una sintaxis de Perl más fuerte en un comentario aquí.


Gracias, no sabía sobre la diferencia entre / y |
Andrea

64
No estoy seguro de que esta respuesta sea útil ... La única diferencia entre s|||y s///es que el carácter separador es diferente y que un personaje no necesita escapar. Igualmente podrías hacer s###. El verdadero problema aquí es que el OP no quiere tener que preocuparse por escapar del contenido de literal_pattern(que no es literal en absoluto y se interpretará como una expresión regular).
Benj

15
Esto no evitará la interpretación de otros caracteres especiales. ¿Qué pasa si busca 1234.*aaacon su solución que coincida mucho más de lo previsto 1234\.\*aaa?
Matteo

20
Esta respuesta no debe aceptarse
Steven Lu

2
Esto pierde el punto por completo. El texto que debe coincidir podría contener cualquier extrañeza. En mi caso es una contraseña aleatoria. Ya sabes cómo van
Christian Bongiorno

13
export FIND='find this'
export REPLACE='replace with this'
ruby -p -i -e "gsub(ENV['FIND'], ENV['REPLACE'])" path/to/file

Esta es la única solución 100% segura aquí, porque:

  • Es una subestación estática, no una expresión regular, no es necesario escapar de nada (por lo tanto, superior al uso sed)
  • No se romperá si su cadena contiene }char (por lo tanto, superior a una solución Perl enviada)
  • No se romperá con ningún personaje, porque ENV['FIND']se usa, no $FIND. Con $FINDo su texto insertado en el código Ruby, puede encontrar un error de sintaxis si su cadena contiene un escape '.

Tuve que usar export FIND='find this; export REPLACE='replace with this';en mi escritura del golpe para que ENV['FIND']e ENV['replace']tenido los valores esperados. Estaba reemplazando algunas cadenas cifradas realmente largas en un archivo. Esto fue solo el boleto.
DMfll

Esta es una buena respuesta, porque es confiable y el rubí es omnipresente. Basado en esta respuesta, ahora uso este script de shell .
loevborg

Lamentablemente no funciona cuando FIND contiene varias líneas.
adrelanos

No hay nada que impida que funcione con varias líneas en FIND. Use comillas dobles \ n.
Nowaker

7

El replacecomando hará esto.

https://linux.die.net/man/1/replace

Cambio en el lugar:

replace text replacement -- path/to/the/file

Para stdout:

replace text replacement < path/to/the/file

Ejemplo:

$ replace '.*' '[^a-z ]{1,3}' <<EOF
> r1: /.*/g
> r2: /.*/gi
> EOF
r1: /[^a-z ]{1,3}/g
r2: /[^a-z ]{1,3}/gi

El replacecomando viene con MySQL o MariaDB.


3
tenga en cuenta que el reemplazo está en desuso y puede no estar disponible en el futuro
Rogelio

1
¿Por qué demonios este comando básico viene con una base de datos?
masterxilo

3
@masterxilo Una mejor pregunta podría ser: ¿por qué un comando tan básico no viene con los sistemas operativos modernos? ;-)
Mark Thomson


3

mira mi script de Perl. hace exactamente lo que necesita sin el uso implícito o explícito de la expresión regular:

https://github.com/Samer-Al-iraqi/Linux-str_replace

str_replace Search Replace File # replace in File in place

STDIN | str_replace Search Replace # to STDOUT

muy práctico ¿verdad? Tuve que aprender Perl para hacerlo. porque realmente realmente lo necesito.


2

Puedes hacerlo escapando de tus patrones. Me gusta esto:

keyword_raw='1/2/3'
keyword_regexp="$(printf '%s' "$keyword_raw" | sed -e 's/[]\/$*.^|[]/\\&/g')"
# keyword_regexp is now '1\/2\/3'

replacement_raw='2/3/4'
replacement_regexp="$(printf '%s' "$replacement_raw" | sed -e 's/[\/&]/\\&/g')"
# replacement_regexp is now '2\/3\/4'

echo 'a/b/c/1/2/3/d/e/f' | sed -e "s/$keyword_regexp/$replacement_regexp/"
# the last command will print 'a/b/c/2/3/4/d/e/f'

Los créditos para estas soluciones van aquí: https://stackoverflow.com/questions/407523/escape-a-string-for-a-sed-replace-pattern

Nota 1: esto solo funciona para palabras clave no vacías. Las palabras clave vacías no son aceptadas por sed ( sed -e 's//replacement/').

Nota 2: desafortunadamente, no conozco una herramienta popular que NO use regexp-s para resolver el problema. Puede escribir dicha herramienta en Rust o C, pero no está allí por defecto.


Esto pierde completamente el punto del OP. Obviamente puedes escapar del patrón, pero para algunos patrones esto es tedioso.
icecreamsword

@icecreamsword, ¿leíste mi respuesta debajo de la primera línea? El script se escapa automáticamente .
VasyaNovikov

1

Reuní algunas otras respuestas y se me ocurrió esto:

function unregex {
   # This is a function because dealing with quotes is a pain.
   # http://stackoverflow.com/a/2705678/120999
   sed -e 's/[]\/()$*.^|[]/\\&/g' <<< "$1"
}
function fsed {
   local find=$(unregex "$1")
   local replace=$(unregex "$2")
   shift 2
   # sed -i is only supported in GNU sed.
   #sed -i "s/$find/$replace/g" "$@"
   perl -p -i -e "s/$find/$replace/g" "$@"
}

No funciona con líneas nuevas. Tampoco ayuda a escapar de las nuevas líneas con \n. ¿Alguna solución?
adrelanos

1

Puede usar str_replace de php :

php -R 'echo str_replace("\|!£$%&/()=?^\"'\''","replace",$argn),PHP_EOL;'<input.txt >output.txt

Nota: Sin embargo, aún necesitaría escapar de comillas simples 'y comillas dobles ".


0

Nodo.JS equivalente de @Nowaker:

export FNAME='moo.txt'
export FIND='search'
export REPLACE='rpl'
node -e 'fs=require("fs");fs.readFile(process.env.FNAME,"utf8",(err,data)=>{if(err!=null)throw err;fs.writeFile(process.env.FNAME,data.replace(process.env.FIND,process.env.REPLACE),"utf8",e=>{if(e!=null)throw e;});});'

0

Aquí hay una forma de trabajo "casi" más.

Use vi o vim.

Cree un archivo de texto con su sustitución en él:

:% sno / mi cadena de búsqueda \\ "-: # 2; g ('. j'); \\"> / my replacecestring = \\ "bac) (o: # 46; \\"> /
:X

luego ejecute vi o vim desde la línea de comandos:

vi -S commandfile.txt path/to/the/file

:% sno es el comando vi para buscar y reemplazar sin magia.

/ es mi separador elegido.

: x guarda y sale vi.

Debe escapar de las barras invertidas '\' la barra diagonal '/' puede reemplazarse con, por ejemplo, un signo de interrogación '?' o algo más que no está en su búsqueda o cadena de reemplazo, tubería '|' No funcionó para mí aunque.

ref: https://stackoverflow.com/questions/6254820/perform-a-non-regex-search-replace-in-vim https://vim.fandom.com/wiki/Search_without_need_to_escape_slash http://linuxcommand.org/ lc3_man_pages / vim1.html

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.