¿Cómo verificar la extensión de un nombre de archivo en un script bash?

170

Estoy escribiendo un script de compilación nocturno en bash.
Todo está bien y elegante a excepción de un pequeño inconveniente:


#!/bin/bash

for file in "$PATH_TO_SOMEWHERE"; do
      if [ -d $file ]
      then
              # do something directory-ish
      else
              if [ "$file" == "*.txt" ]       #  this is the snag
              then
                     # do something txt-ish
              fi
      fi
done;

Mi problema es determinar la extensión del archivo y luego actuar en consecuencia. Sé que el problema está en la declaración if, que prueba un archivo txt.

¿Cómo puedo determinar si un archivo tiene un sufijo .txt?

Anthon
fuente
Esto se romperá si tiene un archivo con un espacio en su nombre.
jfg956
Además de la respuesta de Paul, puede usar $(dirname $PATH_TO_SOMEWHERE)y $(basename $PATH_TO_SOMEWHERE)dividir en carpeta y directorio y hacer algo directorio-ish y file-ish
McPeppr

Respuestas:

249

Creo que quiere decir "¿Son iguales los últimos cuatro caracteres del archivo $ .txt?" Si es así, puede usar lo siguiente:

if [ ${file: -4} == ".txt" ]

Tenga en cuenta que el espacio entre file:y -4es obligatorio, ya que el modificador ': -' significa algo diferente.

Paul Stephenson
fuente
1
para ese fin, también puede cambiar el nombre de command.com a command.txt en una máquina Windows.
Hometoast
9
Si desea especificar una desigualdad, recuerde incluir corchetes adicionales: if [[$ {file: -4}! = ".Txt"]]
Ram Rajamony
44
@RamRajamony ¿Por qué es necesario usar [[cuando se prueba la desigualdad?
PesaEl
Quería señalar que el espacio después del colon es importante. ${var:-4}no es lo mismo que ${var: -4}; el primero (sin espacio) se expandirá como '-4' si var está desarmado el segundo (con un espacio) devuelve los últimos 4 caracteres de var.
pbatey
1
En bash, esto producirá un error "[: ==: operador unario esperado" a menos que ponga comillas alrededor de la primera variable. Entonces en if [ "${file: -4}" == ".txt" ]cambio.
Giles B
264

Hacer

if [ "$file" == "*.txt" ]

Me gusta esto:

if [[ $file == *.txt ]]

Es decir, corchetes dobles y sin comillas.

El lado derecho de ==es un patrón de concha. Si necesita una expresión regular, use =~entonces.


fuente
15
No sabía sobre esto. Parece ser un caso especial que el lado derecho de == o! = Se expanda como un patrón de shell. Personalmente, creo que esto es más claro que mi respuesta.
Paul Stephenson
20
Soy nuevo en bash y me tomó un tiempo descubrir cómo usar esto en una ifdeclaración condicional múltiple . Lo estoy compartiendo aquí en caso de que ayude a alguien. if [[ ( $file == *.csv ) || ( $file == *.png ) ]]
joelostblom
66
@cheflo que es bueno para múltiples condiciones en general. En este caso específico, también podría usar if [[ $file =~ .*\.(csv|png) ]]. Es más corto, más claro, más fácil de agregar extensiones adicionales y se puede configurar fácilmente (poniendo "csv | png" en una variable).
jox
44
Puede poner comillas dobles alrededor del archivo. if [[ "$file" == *.txt ]]Si el archivo tiene espacios en su nombre, se requieren comillas dobles.
shawnhcorey
¿Debo usar esta respuesta en lugar de la aceptada?
Freedo
26

Simplemente no puede estar seguro en un sistema Unix, que un archivo .txt realmente es un archivo de texto. Su mejor apuesta es usar "archivo". Quizás intente usar:

file -ib "$file"

Luego, puede usar una lista de tipos MIME para comparar o analizar la primera parte del MIME donde obtiene cosas como "texto", "aplicación", etc.

Eldelshell
fuente
2
Como file -i...incluye la codificación mime, puede usarfile --mime-type -b ...
Wilf
24

Puede usar el comando "archivo" si realmente desea obtener información sobre el archivo en lugar de confiar en las extensiones.

Si se siente cómodo con el uso de la extensión, puede usar grep para ver si coincide.

Adam Peck
fuente
Sí, estoy al tanto del filecomando. En realidad, intenté hacer coincidir en función de la salida de dicho comando ... pero fallé horriblemente en estas declaraciones if.
17

También puedes hacer:

   if [ "${FILE##*.}" = "txt" ]; then
       # operation for txt files here
   fi
kvz
fuente
11
case $FILE in *.txt ) ... ;; esacparecería más robusto e idiomático.
tripleee
11

Similar a 'archivo', use el 'mimetype -b' un poco más simple que funcionará sin importar la extensión del archivo.

if [ $(mimetype -b "$MyFile") == "text/plain" ]
then
  echo "this is a text file"
fi

Editar: es posible que deba instalar libfile-mimeinfo-perl en su sistema si mimetype no está disponible

Dargaud
fuente
1
Debe dejar en claro que el script 'mimetype' no está disponible en todos los sistemas.
gilbertpilz
Hecho, agregado libfile-mimeinfo-perl
dargaud
5

La respuesta correcta sobre cómo tomar la extensión disponible en un nombre de archivo en Linux es:

${filename##*\.} 

Ejemplo de imprimir todas las extensiones de archivo en un directorio

for fname in $(find . -maxdepth 1 -type f) # only regular file in the current dir
    do  echo ${fname##*\.} #print extensions 
done
Albin Com.
fuente
Su respuesta usa una barra invertida doble, pero su ejemplo solo usa una barra invertida simple. Su ejemplo es correcto, su respuesta no lo es.
gilbertpilz
3

Escribí un script bash que analiza el tipo de archivo y luego lo copia en una ubicación, lo uso para ver los videos que he visto en línea desde mi caché de Firefox:

#!/bin/bash
# flvcache script

CACHE=~/.mozilla/firefox/xxxxxxxx.default/Cache
OUTPUTDIR=~/Videos/flvs
MINFILESIZE=2M

for f in `find $CACHE -size +$MINFILESIZE`
do
    a=$(file $f | cut -f2 -d ' ')
    o=$(basename $f)
    if [ "$a" = "Macromedia" ]
        then
            cp "$f" "$OUTPUTDIR/$o"
    fi
done

nautilus  "$OUTPUTDIR"&

Utiliza ideas similares a las presentadas aquí, espero que esto sea útil para alguien.

desdecode
fuente
2

Supongo que '$PATH_TO_SOMEWHERE'es algo como'<directory>/*' .

En este caso, cambiaría el código a:

find <directory> -maxdepth 1 -type d -exec ... \;
find <directory> -maxdepth 1 -type f -name "*.txt" -exec ... \;

Si desea hacer algo más complicado con el directorio y los nombres de los archivos de texto, puede:

find <directory> -maxdepth 1 -type d | while read dir; do echo $dir; ...; done
find <directory> -maxdepth 1 -type f -name "*.txt" | while read txtfile; do echo $txtfile; ...; done

Si tiene espacios en sus nombres de archivo, podría:

find <directory> -maxdepth 1 -type d | xargs ...
find <directory> -maxdepth 1 -type f -name "*.txt" | xargs ...
jfg956
fuente
Estos son excelentes ejemplos de cómo hacer 'bucles' en shell. Los explícitos fory los whilebucles deben reservarse para cuando el cuerpo del bucle debe ser más complejo.