¿Cómo elimino el sufijo de archivo y la porción de ruta de una cadena de ruta en Bash?


396

Dada una ruta de archivo de cadena como /foo/fizzbuzz.bar, ¿cómo usaría bash para extraer solo la fizzbuzzparte de dicha cadena?


Puede encontrar información en el manual de Bash , buscar ${parameter%word}y ${parameter%%word}en la sección de coincidencia de la parte posterior.
1ac0

Respuestas:


574

Aquí se explica cómo hacerlo con los operadores # y% en Bash.

$ x="/foo/fizzbuzz.bar"
$ y=${x%.bar}
$ echo ${y##*/}
fizzbuzz

${x%.bar}También podría ser ${x%.*}eliminar todo después de un punto o ${x%%.*}eliminar todo después del primer punto.

Ejemplo:

$ x="/foo/fizzbuzz.bar.quux"
$ y=${x%.*}
$ echo $y
/foo/fizzbuzz.bar
$ y=${x%%.*}
$ echo $y
/foo/fizzbuzz

La documentación se puede encontrar en el manual de Bash . Busque ${parameter%word}y ${parameter%%word}rastree la sección correspondiente de la porción.


Terminé usando este porque era el más flexible y había un par de cosas similares que también quería hacer, lo que hizo muy bien.
Lawrence Johnston

Esta es probablemente la más flexible de todas las respuestas publicadas, pero creo que las respuestas que sugieren los comandos basename y dirname también merecen cierta atención. Pueden ser el truco si no necesita ninguna otra combinación de patrones sofisticados.
mgadda

77
¿Cómo se llama esto $ {x% .bar}? Me gustaría saber más al respecto.
Albahaca

14
@Basil: Expansión de parámetros. En una consola, escriba "man bash" y luego escriba "/ expansion de parámetros"
Zan Lynx

1
Supongo que la explicación de 'man bash' tiene sentido si ya sabes lo que hace o si lo probaste por ti mismo de la manera difícil. Es casi tan malo como la referencia de git. Simplemente lo buscaría en Google.
triplebig

227

mira el comando basename:

NAME="$(basename /foo/fizzbuzz.bar .bar)"

11
Probablemente la más simple de todas las soluciones ofrecidas actualmente ... aunque usaría $ (...) en lugar de backticks.
Michael Johnson

66
Más simple pero agrega una dependencia (no una enorme o extraña, lo admito). También necesita saber el sufijo.
Vinko Vrsalovic

Y se puede usar para eliminar cualquier cosa del final, básicamente solo elimina una cadena del final.
Smar

2
El problema es el golpe del tiempo. Acabo de buscar la pregunta para esta discusión después de ver bash tomar casi 5 minutos para procesar 800 archivos, usando basename. Usando el método anterior de expresiones regulares, el tiempo se redujo a aproximadamente 7 segundos. Aunque esta respuesta es más fácil de realizar para el programador, el golpe de tiempo es demasiado. ¡Imagina una carpeta con un par de miles de archivos en ella! Tengo algunas de esas carpetas.
xizdaqrian

@xizdaqrian Esto es absolutamente falso. Este es un programa simple, que no debería tomar medio segundo para regresar. Acabo de ejecutar time find / home / me / dev -name "* .py" .py -exec basename {} \; y eliminó la extensión y el directorio de 1500 archivos en 1 segundo en total.
Laszlo Treszkai

40

Golpe puro, hecho en dos operaciones separadas:

  1. Eliminar la ruta de una cadena de ruta:

    path=/foo/bar/bim/baz/file.gif
    
    file=${path##*/}  
    #$file is now 'file.gif'
  2. Elimine la extensión de una cadena de ruta:

    base=${file%.*}
    #${base} is now 'file'.

18

Usando basename utilicé lo siguiente para lograr esto:

for file in *; do
    ext=${file##*.}
    fname=`basename $file $ext`

    # Do things with $fname
done;

Esto no requiere un conocimiento a priori de la extensión del archivo y funciona incluso cuando tiene un nombre de archivo que tiene puntos en su nombre de archivo (delante de su extensión); sin embargo, requiere el programa basename, pero esto es parte de los coreutils de GNU, por lo que debe enviarse con cualquier distribución.


1
Excelente respuesta! elimina la extensión de una manera muy limpia, pero no elimina el. al final del nombre del archivo.
metrix

3
@metrix solo agrega el "." antes de $ ext, es decir: fname=`basename $file .$ext`
Carlos Troncoso

13

Pura manera bash:

~$ x="/foo/bar/fizzbuzz.bar.quux.zoom"; 
~$ y=${x/\/*\//}; 
~$ echo ${y/.*/}; 
fizzbuzz

Esta funcionalidad se explica en man bash en "Expansión de parámetros". Abundan las formas no bash: awk, perl, sed, etc.

EDITAR: funciona con puntos en sufijos de archivo y no necesita conocer el sufijo (extensión), pero no funciona con puntos en el nombre mismo.


11

Las funciones basename y dirname son lo que buscas:

mystring=/foo/fizzbuzz.bar
echo basename: $(basename "${mystring}")
echo basename + remove .bar: $(basename "${mystring}" .bar)
echo dirname: $(dirname "${mystring}")

Tiene salida:

basename: fizzbuzz.bar
basename + remove .bar: fizzbuzz
dirname: /foo

1
Sería útil arreglar la cita aquí; tal vez ejecute esto a través de shellcheck.net con mystring=$1el valor constante actual en lugar del valor constante (que suprimirá varias advertencias, asegurándose de no contener espacios / caracteres globales / etc.) y aborde los problemas. encuentra?
Charles Duffy

Bueno, hice algunos cambios apropiados para apoyar las comillas en $ mystring. Gosh esto fue hace mucho tiempo escribí esto :)
Jerub

Sería una mejora adicional citar los resultados: de echo "basename: $(basename "$mystring")"esa manera, si mystring='/foo/*'no se *reemplaza con una lista de archivos en el directorio actual después de basename finalizar.
Charles Duffy

6

Usar basenamesupone que sabes cuál es la extensión del archivo, ¿no?

Y creo que las diversas sugerencias de expresiones regulares no hacen frente a un nombre de archivo que contiene más de un "."

Lo siguiente parece hacer frente a los puntos dobles. Ah, y los nombres de archivo que contienen un "/" en sí (solo por diversión)

Parafraseando a Pascal, "Lo siento, este guión es tan largo. No tuve tiempo para acortarlo"


  #!/usr/bin/perl
  $fullname = $ARGV[0];
  ($path,$name) = $fullname =~ /^(.*[^\\]\/)*(.*)$/;
  ($basename,$extension) = $name =~ /^(.*)(\.[^.]*)$/;
  print $basename . "\n";
 

1
Esto es agradable y robusto
Gaurav Jain

4

Además de la sintaxis conforme POSIX utilizada en esta respuesta ,

basename string [suffix]

como en

basename /foo/fizzbuzz.bar .bar

GNUbasename admite otra sintaxis:

basename -s .bar /foo/fizzbuzz.bar

con el mismo resultado La diferencia y la ventaja es que eso -simplica -a, que admite múltiples argumentos:

$ basename -s .bar /foo/fizzbuzz.bar /baz/foobar.bar
fizzbuzz
foobar

Esto incluso puede hacerse seguro para el nombre de archivo separando la salida con bytes NUL usando la -zopción, por ejemplo, para estos archivos que contienen espacios en blanco, líneas nuevas y caracteres globales (entre comillas ls):

$ ls has*
'has'$'\n''newline.bar'  'has space.bar'  'has*.bar'

Lectura en una matriz:

$ readarray -d $'\0' arr < <(basename -zs .bar has*)
$ declare -p arr
declare -a arr=([0]=$'has\nnewline' [1]="has space" [2]="has*")

readarray -drequiere Bash 4.4 o posterior. Para versiones anteriores, tenemos que hacer un bucle:

while IFS= read -r -d '' fname; do arr+=("$fname"); done < <(basename -zs .bar has*)

Además, el sufijo especificado se elimina en la salida si está presente (y se ignora de lo contrario).
aksh1618


3

Si no puede usar basename como se sugiere en otras publicaciones, siempre puede usar sed. Aquí hay un ejemplo (feo). No es el mejor, pero funciona extrayendo la cadena deseada y reemplazando la entrada con la cadena deseada.

echo '/foo/fizzbuzz.bar' | sed 's|.*\/\([^\.]*\)\(\..*\)$|\1|g'

Que te dará la salida

fizzbuzz


Aunque esta es la respuesta a la pregunta original, este comando es útil cuando tengo líneas de rutas en un archivo para extraer nombres base para imprimirlos en la pantalla.
Sangcheol Choi

2

Tenga cuidado con la solución perl sugerida: elimina cualquier cosa después del primer punto.

$ echo some.file.with.dots | perl -pe 's/\..*$//;s{^.*/}{}'
some

Si quieres hacerlo con perl, esto funciona:

$ echo some.file.with.dots | perl -pe 's/(.*)\..*$/$1/;s{^.*/}{}'
some.file.with

Pero si está utilizando Bash, las soluciones con y=${x%.*}(o basename "$x" .extsi conoce la extensión) son mucho más simples.


1

El nombre base hace eso, elimina el camino. También eliminará el sufijo si se proporciona y si coincide con el sufijo del archivo, pero necesitaría saber el sufijo para darle el comando. De lo contrario, puede usar mv y descubrir cuál debería ser el nuevo nombre de otra manera.


1

Combinando la respuesta mejor calificada con la segunda respuesta mejor calificada para obtener el nombre de archivo sin la ruta completa:

$ x="/foo/fizzbuzz.bar.quux"
$ y=(`basename ${x%%.*}`)
$ echo $y
fizzbuzz

¿Por qué estás usando una matriz aquí? Además, ¿por qué usar basename?
codeforester
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.