Eliminar un prefijo / sufijo fijo de una cadena en Bash

485

En mi bashscript tengo una cadena y su prefijo / sufijo. Necesito eliminar el prefijo / sufijo de la cadena original.

Por ejemplo, supongamos que tengo los siguientes valores:

string="hello-world"
prefix="hell"
suffix="ld"

¿Cómo llego al siguiente resultado?

result="o-wor"
Dušan Rychnovský
fuente
55
Echa un vistazo Guía avanzada de secuencias de comandos Bash
tarrsalah
14
Tenga mucho cuidado al vincular a la llamada Guía avanzada de secuencias de comandos Bash; Contiene una mezcla de buenos consejos y terribles.
tripleee

Respuestas:

720
$ foo=${string#"$prefix"}
$ foo=${foo%"$suffix"}
$ echo "${foo}"
o-wor
Adrian Frühwirth
fuente
40
También hay ## y %%, que eliminan tanto como sea posible si $ prefix o $ suffix contienen comodines.
pts
28
¿Hay alguna manera de combinar los dos en una línea? Lo intenté ${${string#prefix}%suffix}pero no funciona.
static_rtti
28
@static_rtti No, desafortunadamente no puede anidar la sustitución de parámetros de esta manera. Lo sé, es una pena.
Adrian Frühwirth
87
@ AdrianFrühwirth: todo el lenguaje es una pena, pero es muy útil :)
static_rtti
8
Nvm, "sustitución de bash" en Google encontró lo que quería.
Tyler
89

Usando sed:

$ echo "$string" | sed -e "s/^$prefix//" -e "s/$suffix$//"
o-wor

Dentro del comando sed, el ^ carácter coincide con el texto que comienza con $prefix, y el final $coincide con el texto que termina con $suffix.

Adrian Frühwirth hace algunos buenos puntos en los comentarios a continuación, pero sed para este propósito puede ser muy útil. El hecho de que los contenidos de $ prefix y $ suffix son interpretados por sed puede ser bueno o malo, siempre que prestes atención, deberías estar bien. La belleza es que puedes hacer algo como esto:

$ prefix='^.*ll'
$ suffix='ld$'
$ echo "$string" | sed -e "s/^$prefix//" -e "s/$suffix$//"
o-wor

que puede ser lo que quieras, y es más elegante y más poderoso que la sustitución de variables bash. Si recuerdas que con un gran poder viene una gran responsabilidad (como dice Spiderman), deberías estar bien.

Se puede encontrar una introducción rápida a sed en http://evc-cit.info/cit052/sed_tutorial.html

Una nota sobre el shell y su uso de cadenas:

Para el ejemplo particular dado, lo siguiente funcionaría también:

$ echo $string | sed -e s/^$prefix// -e s/$suffix$//

... pero solo porque:

  1. echo no le importa cuántas cadenas hay en su lista de argumentos, y
  2. No hay espacios en $ prefix y $ suffix

En general, es una buena práctica citar una cadena en la línea de comando porque incluso si contiene espacios, se presentará al comando como un argumento único. Citamos $ prefix y $ suffix por la misma razón: cada comando de edición para sed se pasará como una cadena. Usamos comillas dobles porque permiten la interpolación variable; Si hubiéramos usado comillas simples, el comando sed habría obtenido un literal $prefixy $suffixque ciertamente no es lo que queríamos.

Observe también mi uso de comillas simples al establecer las variables prefixy suffix. Ciertamente no queremos que se interprete nada en las cadenas, por lo que las citamos con una sola cita para que no se realice ninguna interpolación. De nuevo, puede que no sea necesario en este ejemplo, pero es un muy buen hábito.

Chris Kolodin
fuente
8
Desafortunadamente, este es un mal consejo por varias razones: 1) Sin comillas, $stringestá sujeto a la división de palabras y el bloqueo. 2) $prefixy $suffixpuede contener expresiones que sedinterpretarán, por ejemplo, expresiones regulares o el carácter utilizado como delimitador que romperá todo el comando. 3) No sedes necesario llamar dos veces (puede -e 's///' -e '///'hacerlo) y también se puede evitar la tubería. Por ejemplo, considere string='./ *'y / o prefix='./'y vea cómo se rompe horriblemente debido a 1)y 2).
Adrian Frühwirth
Nota divertida: sed puede tomar casi cualquier cosa como delimitador. En mi caso, dado que estaba analizando directorios de prefijos fuera de las rutas, no pude usar /, por lo que usé sed "s#^$prefix##. (Fragilidad: los nombres de archivo no pueden contener #. Dado que controlo los archivos, estamos a salvo allí).
Olie
@Olie Filenames puede contener cualquier carácter, excepto la barra inclinada y el carácter nulo, por lo que, a menos que tenga el control, no puede suponer que un nombre de archivo no contenga ciertos caracteres.
Adrian Frühwirth
Sí, no sé lo que estaba pensando allí. ¿iOS tal vez? No sé. Los nombres de archivo ciertamente pueden contener "#". No tengo idea de por qué dije eso. :)
Olie
@Olie: Como entendí tu comentario original, estabas diciendo que la limitación de tu elección de usar #como delimitador de sed significaba que no podías manejar archivos que contengan ese carácter.
P Daddy
17

¿Conoces la longitud de tu prefijo y sufijo? En tu caso:

result=$(echo $string | cut -c5- | rev | cut -c3- | rev)

O más general:

result=$(echo $string | cut -c$((${#prefix}+1))- | rev | cut -c$((${#suffix}+1))- | rev)

¡Pero la solución de Adrian Frühwirth es genial! ¡No sabía sobre eso!

tommy.carstensen
fuente
14

Uso grep para eliminar prefijos de rutas (que no se manejan bien sed):

echo "$input" | grep -oP "^$prefix\K.*"

\K elimina del partido todos los personajes anteriores.

Vladimir Petrakovich
fuente
grep -PEs una extensión no estándar. Más potencia para usted si es compatible con su plataforma, pero este es un consejo dudoso si su código debe ser razonablemente portátil.
tripleee
@tripleee De hecho. Pero creo que un sistema con GNU Bash instalado también tiene un grep que admite PCRE.
Vladimir Petrakovich
1
No, MacOS, por ejemplo, tiene Bash listo para usar, pero no GNU grep. Las versiones anteriores en realidad tenían la -Popción de BSD greppero la eliminaron.
tripleee
9
$ string="hello-world"
$ prefix="hell"
$ suffix="ld"

$ #remove "hell" from "hello-world" if "hell" is found at the beginning.
$ prefix_removed_string=${string/#$prefix}

$ #remove "ld" from "o-world" if "ld" is found at the end.
$ suffix_removed_String=${prefix_removed_string/%$suffix}
$ echo $suffix_removed_String
o-wor

Notas:

# $ prefijo: agregar # asegura que la subcadena "infierno" se elimine solo si se encuentra al principio. % $ sufijo: agregar% asegura que la subcadena "ld" se elimine solo si se encuentra al final.

Sin estos, las subcadenas "infierno" y "ld" se eliminarán en todas partes, incluso si se encuentran en el medio.

Vijay Vat
fuente
Gracias por las notas! qq: en su ejemplo de código también tiene una barra diagonal /justo después de la cadena, ¿para qué sirve?
DiegoSalazar
1
/ separa la cadena actual y la subcadena. La subcadena aquí es el sufijo en la pregunta publicada.
Vijay Vat
7

Usando el =~operador :

$ string="hello-world"
$ prefix="hell"
$ suffix="ld"
$ [[ "$string" =~ ^$prefix(.*)$suffix$ ]] && echo "${BASH_REMATCH[1]}"
o-wor
Martin - マ ー チ ン
fuente
6

Solución pequeña y universal:

expr "$string" : "$prefix\(.*\)$suffix"
Tosi Do
fuente
1
Si está usando Bash, probablemente no debería estar usando expren absoluto. Era una especie de utilidad de fregadero de cocina conveniente en los días de la carcasa original de Bourne, pero ahora ha pasado su fecha de caducidad.
tripleee
5

Usando la respuesta de @Adrian Frühwirth:

function strip {
    local STRING=${1#$"$2"}
    echo ${STRING%$"$2"}
}

úsalo así

HELLO=":hello:"
HELLO=$(strip "$HELLO" ":")
echo $HELLO # hello
matemáticas2001
fuente
0

Haría uso de grupos de captura en regex:

$ string="hello-world"
$ prefix="hell"
$ suffix="ld"
$ set +H # Disables history substitution, can be omitted in scripts.
$ perl -pe "s/${prefix}((?:(?!(${suffix})).)*)${suffix}/\1/" <<< $string
o-wor
$ string1=$string$string
$ perl -pe "s/${prefix}((?:(?!(${suffix})).)*)${suffix}/\1/g" <<< $string1
o-woro-wor

((?:(?!(${suffix})).)*)se asegura de que el contenido de ${suffix}se excluirá del grupo de captura. En términos de ejemplo, es la cadena equivalente a [^A-Z]*. De lo contrario, obtendrá:

$ perl -pe "s/${prefix}(.*)${suffix}/\1/g" <<< $string1
o-worldhello-wor
Brazo pantanoso
fuente