Come controllare l'estensione di un nome file in uno script bash?


170

Sto scrivendo un copione notturno di build in bash.
Tutto va bene e dandy tranne un piccolo 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;

Il mio problema è determinare l'estensione del file e quindi agire di conseguenza. So che il problema è nell'istruzione if, che sta testando un file txt.

Come posso determinare se un file ha un suffisso .txt?


Questo si interromperà se si dispone di un file con uno spazio nel suo nome.
jfg956,

Oltre alla risposta di Paul, puoi usare $(dirname $PATH_TO_SOMEWHERE)e $(basename $PATH_TO_SOMEWHERE)dividere in cartelle e directory e fare qualcosa di directory-ish e file-ish
McPeppr,

Risposte:


248

Penso che tu voglia dire "Gli ultimi quattro caratteri di $ file sono uguali .txt?" In tal caso, è possibile utilizzare quanto segue:

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

Si noti che lo spazio tra file:e -4è richiesto, poiché il modificatore ': -' significa qualcosa di diverso.


1
a tal fine, è possibile rinominare command.com in command.txt anche su un computer Windows.
hometoast

9
Se vuoi specificare una disuguaglianza, ricorda di includere parentesi extra: if [[$ {file: -4}! = ".Txt"]]
Ram Rajamony,

4
@RamRajamony Perché è necessario utilizzare [[quando si verifica la disuguaglianza?
Pesa L'

Volevo sottolineare che lo spazio dopo il colon è importante. ${var:-4}non è lo stesso di ${var: -4}; il primo (senza spazio) si espanderà come '-4' se var non è impostato il secondo (con uno spazio) restituisce gli ultimi 4 caratteri di var.
pbatey,

1
In bash, questo produrrà un errore "[: ==: operatore unario previsto" a meno che tu non inserisca virgolette attorno alla prima variabile. Quindi if [ "${file: -4}" == ".txt" ]invece.
Giles B,

264

Rendere

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

come questo:

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

Cioè, doppie parentesi e senza virgolette.

Il lato destro di ==è un motivo a conchiglia. Se hai bisogno di un'espressione regolare, usa =~allora.


15
Non lo sapevo. Sembra essere un caso speciale che il lato destro di == o! = Sia espanso come modello di shell. Personalmente penso che questo sia più chiaro della mia risposta.
Paul Stephenson,

20
Sono nuovo a bash e mi ci è voluto un po 'di tempo per capire come usarlo in una ifdichiarazione multi condizionale . Lo condivido qui nel caso in cui aiuti qualcuno. if [[ ( $file == *.csv ) || ( $file == *.png ) ]]
Joelostblom,

6
@cheflo va bene per molteplici condizioni in generale. In questo caso specifico, potresti anche usare if [[ $file =~ .*\.(csv|png) ]]. È più breve, più chiaro, più facile aggiungere ulteriori estensioni e può essere facilmente reso configurabile (inserendo "csv | png" in una variabile).
jox

4
È possibile inserire virgolette doppie attorno al file. if [[ "$file" == *.txt ]]Se il file ha spazi nel suo nome, è richiesta la doppia virgoletta.
shawnhcorey,

Dovrei usare questa risposta invece di quella accettata?
Freedo,

26

Non puoi essere sicuro su un sistema Unix, che un file .txt è veramente un file di testo. La tua scommessa migliore è usare "file". Forse prova a usare:

file -ib "$file"

Quindi è possibile utilizzare un elenco di tipi MIME per abbinare o analizzare la prima parte del MIME in cui si ottengono elementi come "testo", "applicazione", ecc.


2
Come file -i...include la codifica MIME, è possibile utilizzarefile --mime-type -b ...
Wilf

24

È possibile utilizzare il comando "file" se si desidera effettivamente trovare informazioni sul file anziché fare affidamento sulle estensioni.

Se ti senti a tuo agio con l'uso dell'estensione, puoi usare grep per vedere se corrisponde.


si, sono a conoscenza del filecomando. In realtà avevo provato ad abbinare in base all'output di detto comando ... ma non riesco orribilmente a queste dichiarazioni if.

17

Puoi anche fare:

   if [ "${FILE##*.}" = "txt" ]; then
       # operation for txt files here
   fi

11
case $FILE in *.txt ) ... ;; esacsembrerebbe più robusto e idiomatico.
Tripleee,

11

Simile a 'file', usa il 'mimetype -b' leggermente più semplice che funzionerà indipendentemente dall'estensione del file.

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

Modifica: potrebbe essere necessario installare libfile-mimeinfo-perl sul tuo sistema se mimetype non è disponibile


1
Dovresti chiarire che lo script "mimetype" non è disponibile su tutti i sistemi.
Gilbertpilz,

Fatto, aggiunto libfile-mimeinfo-perl
dargaud,

5

La risposta corretta su come rendere l'estensione disponibile in un nome file in Linux è:

${filename##*\.} 

Esempio di stampa di tutte le estensioni di file in una directory

for fname in $(find . -maxdepth 1 -type f) # only regular file in the current dir
    do  echo ${fname##*\.} #print extensions 
done

La tua risposta utilizza una doppia barra rovesciata, ma il tuo esempio utilizza una sola barra rovesciata. Il tuo esempio è corretto, la tua risposta no.
Gilbertpilz,

3

Ho scritto uno script bash che guarda il tipo di un file e poi lo copia in una posizione, lo uso per guardare attraverso i video che ho visto online dalla mia cache di 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"&

Usa idee simili a quelle presentate qui, spero che questo sia utile a qualcuno.


2

Immagino che '$PATH_TO_SOMEWHERE'sia qualcosa del genere '<directory>/*'.

In questo caso, cambierei il codice in:

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

Se vuoi fare qualcosa di più complicato con la directory e i nomi dei file di testo, puoi:

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

Se hai spazi nei nomi dei file, puoi:

find <directory> -maxdepth 1 -type d | xargs ...
find <directory> -maxdepth 1 -type f -name "*.txt" | xargs ...

Questi sono ottimi esempi di come si eseguono i "loop" nella shell. È meglio riservare espliciti fore whileloop quando il corpo del loop deve essere più complesso.
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.