Agarrando la extensión en un nombre de archivo

33

¿Cómo obtengo la extensión de archivo de bash? Esto es lo que probé:

filename=`basename $filepath`
fileext=${filename##*.}

Al hacerlo, puedo obtener una extensión de bz2la ruta /dir/subdir/file.bz2, pero tengo un problema con la ruta /dir/subdir/file-1.0.tar.bz2.

Preferiría una solución usando solo bash sin programas externos si es posible.

Para aclarar mi pregunta, estaba creando un script bash para extraer cualquier archivo dado con un solo comando de extract path_to_file. La secuencia de comandos determina cómo extraer el archivo al ver su tipo de compresión o archivo, que podría ser .tar.gz, .gz, .bz2, etc. Creo que esto debería implicar la manipulación de cadenas, por ejemplo, si obtengo la extensión, .gzentonces debe verificar si tiene la cadena .tarantes .gz; de ser así, la extensión debería ser .tar.gz.

uray
fuente
2
file = "/ dir / subdir / file-1.0.tar.bz2"; echo $ {file ## *.} imprime '.bz2' aquí. ¿Cuál es el resultado que estás esperando?
axel_c
1
necesito.tar.bz2
uray

Respuestas:

19

Si el nombre del archivo es file-1.0.tar.bz2, la extensión es bz2. El método que está utilizando para extraer la extensión ( fileext=${filename##*.}) es perfectamente válido¹.

¿Cómo decides que quieres que la extensión sea o tar.bz2no ? Primero debes responder esta pregunta. Luego puede averiguar qué comando de shell coincide con su especificación.bz20.tar.bz2

  • Una posible especificación es que las extensiones deben comenzar con una letra. Esta heurística falla para algunas extensiones comunes como 7z, que podrían tratarse mejor como un caso especial. Aquí hay una implementación bash / ksh / zsh:

    basename=$filename; fileext=
    while [[ $basename = ?*.* &&
             ( ${basename##*.} = [A-Za-z]* || ${basename##*.} = 7z ) ]]
    do
      fileext=${basename##*.}.$fileext
      basename=${basename%.*}
    done
    fileext=${fileext%.}

    Para la portabilidad POSIX, debe usar una caseinstrucción para la coincidencia de patrones.

    while case $basename in
            ?*.*) case ${basename##*.} in [A-Za-z]*|7z) true;; *) false;; esac;;
            *) false;;
          esac
    do 
  • Otra posible especificación es que algunas extensiones denotan codificaciones e indican que se necesita una mayor eliminación. Aquí hay una implementación bash / ksh / zsh (que requiere shopt -s extglobbajo bash y setopt ksh_globbajo zsh):

    basename=$filename
    fileext=
    while [[ $basename = ?*.@(bz2|gz|lzma) ]]; do
      fileext=${basename##*.}.$fileext
      basename=${basename%.*}
    done
    if [[ $basename = ?*.* ]]; then
      fileext=${basename##*.}.$fileext
      basename=${basename%.*}
    fi
    fileext=${fileext%.}

    Tenga en cuenta que esto se considera 0una extensión en file-1.0.gz.

¹ y las construcciones relacionadas están en POSIX , por lo que funcionan en cualquier shell de estilo Bourne no antiguo, como ash, bash, ksh o zsh. ${VARIABLE##SUFFIX}

Gilles 'SO- deja de ser malvado'
fuente
eso debería resolverse, verificando si la cadena antes del último .token es de tipo archivo, por ejemplo tar, si su tipo de archivo no 0debería terminar, como la iteración.
uray
2
@uray: eso funciona en este caso particular, pero no es una solución general. Considere el ejemplo de Maciej de.patch.lzma . Una mejor heurística sería considerar la cadena después de la última .: si se trata de un sufijo de compresión ( .7z, .bz2, .gz, ...), continuar extracción.
Gilles 'SO- deja de ser malvado'
@NoamM ¿Qué estaba mal con la sangría? Definitivamente se rompe después de su edición: el código doblemente anidado se sangra igual que anidado individualmente.
Gilles 'SO- deja de ser malvado'
22

Puede simplificar las cosas simplemente haciendo coincidir patrones en el nombre de archivo en lugar de extraer la extensión dos veces:

case "$filename" in
    *.tar.bz2) bunzip_then_untar ;;
    *.bz2)     bunzip_only ;;
    *.tar.gz)  untar_with -z ;;
    *.tgz)     untar_with -z ;;
    *.gz)      gunzip_only ;;
    *.zip)     unzip ;;
    *.7z)      do something ;;
    *)         do nothing ;;
esac
Glenn Jackman
fuente
Esta solución es bellamente simple.
AsymLabs
2

Aquí está mi oportunidad: Traducir puntos a nuevas líneas, canalizar tail, obtener la última línea:

$> TEXT=123.234.345.456.456.567.678
$> echo $TEXT | tr . \\n | tail -n1
678
Michael Bar-Sinai
fuente
0
echo ${filename#$(echo $filename | sed 's/\.[^[:digit:]].*$//g;')}

Por ejemplo:

% echo $filename
2.6.35-zen2.patch.lzma
% echo ${filename#$(echo $filename | sed 's/\.[^[:digit:]].*$//g;')}
.patch.lzma
Maciej Piechotka
fuente
No funciona para todos los casos. Probar con 'foo.7z'
axel_c
Necesita comillas y mejor uso printfen caso de que el nombre del archivo contenga una barra diagonal inversa o comience con -:"${filename#$(printf %s "$filename" | sed 's/\.[^[:digit:]].*$//g;')}"
Gilles 'SO- deja de ser malvado'
@axel_c: correcto, y he implementado la misma especificación que Maciej como ejemplo. ¿Qué heurística sugieres que sea mejor que "comienza con una letra"?
Gilles 'SO- deja de ser malvado'
1
@Gilles: creo que no hay una solución a menos que use una lista precalculada de extensiones conocidas, porque una extensión puede ser cualquier cosa.
axel_c
0

Un día he creado esas funciones difíciles:

# args: string how_many
function get_last_letters(){ echo ${1:${#1}-$2:$2}; }
function cut_last_letters(){ echo ${1:0:${#1}-$2}; }

He encontrado este enfoque sencillo, muy útil en muchos casos, no solo cuando se trata de extensiones.

Para verificar extensiones: es simple y confiable

~$ get_last_letters file.bz2 4
.bz2
~$ get_last_letters file.0.tar.bz2 4
.bz2

Para extensión de corte:

~$ cut_last_letters file.0.tar.bz2 4
file.0.tar

Para cambiar la extensión:

~$ echo $(cut_last_letters file.0.tar.bz2 4).gz
file.0.tar.gz

O, si te gustan las "funciones prácticas:

~$ function cut_last_letters_and_add(){ echo ${1:0:${#1}-$2}"$3"; }
~$ cut_last_letters_and_add file.0.tar.bz2 4 .gz
file.0.tar.gz

PD: Si le gustaron esas funciones o las encontró usadas, consulte esta publicación :) (y esperemos que haga un comentario).

Grzegorz Wierzowiecki
fuente
0

la respuesta basada en casos de jackman es bastante buena y portátil, pero si solo desea el nombre de archivo y la extensión en una variable, he encontrado esta solución:

INPUTFILE="$1"
INPUTFILEEXT=$( echo -n "$INPUTFILE" | rev | cut -d'.' -f1 | rev )
INPUTFILEEXT=$( echo -n $INPUTFILEEXT | tr '[A-Z]' '[a-z]' ) # force lowercase extension
INPUTFILENAME="`echo -n \"$INPUTFILE\" | rev | cut -d'.' -f2- | rev`"

# fix for files with multiple extensions like "gbamidi-v1.0.tar.gz"
INPUTFILEEXT2=$( echo -n "$INPUTFILENAME" | rev | cut -d'.' -f1 | rev )
if [ "$INPUTFILEEXT2" = "tar" ]; then
    # concatenate the extension
    INPUTFILEEXT="$INPUTFILEEXT2.$INPUTFILEEXT"
    # update the filename
    INPUTFILENAME="`echo -n \"$INPUTFILENAME\" | rev | cut -d'.' -f2- | rev`"
fi

Solo funciona con extensiones dobles y la primera debe ser "tar".

Pero puede cambiar la línea de prueba "tar" con una prueba de longitud de cadena y repetir la corrección varias veces.

Eadmaster
fuente
-1

Lo resolví usando esto:

filename=`basename $filepath`
fileext=${filename##*.}
fileext2=${filename%.*}
fileext3=${fileext2##*.}
if [ "$fileext3" == "tar" ]; then
    fileext="tar."$fileext
fi

pero esto solo funciona para el tipo de archivo conocido, en este caso solo tar

uray
fuente