¿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?

Lawrence Johnston
fuente
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.

Zan Lynx
fuente
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)"
zigdon
fuente
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'.
Param
fuente
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.

Miguel
fuente
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.

Vinko Vrsalovic
fuente
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
Jerub
fuente
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";
 
Andrew Edgecombe
fuente
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*)
Benjamin W.
fuente
Además, el sufijo especificado se elimina en la salida si está presente (y se ignora de lo contrario).
aksh1618
3
perl -pe 's/\..*$//;s{^.*/}{}'
mopoke
fuente
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

nymacro
fuente
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.

mivk
fuente
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.

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