Wie überprüfe ich die Erweiterung eines Dateinamens in einem Bash-Skript?

168

Ich schreibe ein nächtliches Build-Skript in Bash.
Alles ist gut und gut, bis auf einen kleinen Haken:


#!/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;

Mein Problem besteht darin, die Dateierweiterung zu ermitteln und dann entsprechend zu handeln. Ich weiß, dass das Problem in der if-Anweisung liegt, die nach einer txt-Datei sucht.

Wie kann ich feststellen, ob eine Datei das Suffix .txt hat?

Anthon
quelle
Dies wird unterbrochen, wenn Sie eine Datei mit einem Leerzeichen im Namen haben.
jfg956
Zusätzlich zur Antwort von Paul können Sie verwenden $(dirname $PATH_TO_SOMEWHERE)und $(basename $PATH_TO_SOMEWHERE)in Ordner und Verzeichnis aufteilen und etwas Verzeichnis-ish und file-ish
tun

Antworten:

246

Ich denke, Sie möchten sagen: "Sind die letzten vier Zeichen von $ file gleich .txt?" In diesem Fall können Sie Folgendes verwenden:

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

Beachten Sie, dass der Abstand zwischen file:und -4erforderlich ist, da der Modifikator ': -' etwas anderes bedeutet.

Paul Stephenson
quelle
1
Zu diesem Zweck können Sie command.com auch auf einem Windows-Computer in command.txt umbenennen.
Hometoast
9
Wenn Sie eine Ungleichung angeben möchten, denken Sie daran, zusätzliche Klammern
einzufügen
4
@RamRajamony Warum muss beim Testen der Ungleichung [[] verwendet werden?
PesaDer
Ich wollte darauf hinweisen, dass der Raum nach dem Doppelpunkt wichtig ist. ${var:-4}ist nicht dasselbe wie ${var: -4}; Das erste (ohne Leerzeichen) wird als '-4' erweitert, wenn var deaktiviert ist. Das zweite (mit Leerzeichen) gibt die letzten 4 Zeichen von var zurück.
pbatey
1
In bash wird der Fehler "[: ==: unärer Operator erwartet" ausgegeben, es sei denn, Sie setzen Anführungszeichen um die erste Variable. Also if [ "${file: -4}" == ".txt" ]stattdessen.
Giles B
264

Machen

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

so was:

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

Das heißt, doppelte Klammern und keine Anführungszeichen.

Die rechte Seite ==ist ein Muschelmuster. Wenn Sie einen regulären Ausdruck benötigen, verwenden Sie =~dann.


quelle
15
Ich wusste nichts davon. Es scheint ein Sonderfall zu sein, dass die rechte Seite von == oder! = Als Shell-Muster erweitert wird. Persönlich denke ich, dass dies klarer ist als meine Antwort.
Paul Stephenson
20
Ich bin neu im Bash und es hat eine Weile gedauert, bis ich herausgefunden habe, wie ich das in einer multikonditionalen ifAnweisung verwenden kann. Ich teile es hier, falls es jemandem hilft. if [[ ( $file == *.csv ) || ( $file == *.png ) ]]
Joelostblom
6
@cheflo das ist gut für mehrere Bedingungen im Allgemeinen. In diesem speziellen Fall können Sie auch verwenden if [[ $file =~ .*\.(csv|png) ]]. Es ist kürzer, übersichtlicher, einfacher, zusätzliche Erweiterungen hinzuzufügen, und kann leicht konfigurierbar gemacht werden (indem "csv | png" in eine Variable eingefügt wird).
Jox
4
Sie können die Datei in doppelte Anführungszeichen setzen. if [[ "$file" == *.txt ]]Wenn der Name der Datei Leerzeichen enthält, ist ein doppeltes Anführungszeichen erforderlich.
Shawnhcorey
Soll ich diese Antwort anstelle der akzeptierten verwenden?
Freedo
26

Auf einem Unix-System kann man einfach nicht sicher sein, dass eine TXT-Datei wirklich eine Textdatei ist. Am besten verwenden Sie "Datei". Vielleicht versuchen Sie es mit:

file -ib "$file"

Anschließend können Sie eine Liste von MIME-Typen verwenden, um den ersten Teil des MIME abzugleichen oder zu analysieren, in dem Sie Informationen wie "Text", "Anwendung" usw. erhalten.

Eldelshell
quelle
2
Wie file -i...die MIME-Codierung enthält, können Sie verwendenfile --mime-type -b ...
Wilf
24

Sie können den Befehl "Datei" verwenden, wenn Sie tatsächlich Informationen über die Datei erhalten möchten, anstatt sich auf die Erweiterungen zu verlassen.

Wenn Sie mit der Erweiterung vertraut sind, können Sie mit grep prüfen, ob sie übereinstimmt.

Adam Peck
quelle
Ja, mir ist der fileBefehl bekannt. Ich hatte tatsächlich versucht, einen Matching basierend auf der Ausgabe des Befehls durchzuführen ... aber ich versage schrecklich bei diesen if-Anweisungen.
17

Sie könnten auch tun:

   if [ "${FILE##*.}" = "txt" ]; then
       # operation for txt files here
   fi
kvz
quelle
11
case $FILE in *.txt ) ... ;; esacwürde robuster und idiomatischer erscheinen.
Tripleee
11

Verwenden Sie ähnlich wie bei 'file' den etwas einfacheren 'mimetype -b', der unabhängig von der Dateierweiterung funktioniert.

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

Bearbeiten: Möglicherweise müssen Sie libfile-mimeinfo-perl auf Ihrem System installieren, wenn der Mimetyp nicht verfügbar ist

Dargaud
quelle
1
Sie sollten klarstellen, dass das Skript 'mimetype' nicht auf allen Systemen verfügbar ist.
Gilbertpilz
Fertig, hinzugefügt libfile-mimeinfo-perl
dargaud
5

Die richtige Antwort, wie Sie die in einem Dateinamen unter Linux verfügbare Erweiterung verwenden können, lautet:

${filename##*\.} 

Beispiel für das Drucken aller Dateierweiterungen in einem Verzeichnis

for fname in $(find . -maxdepth 1 -type f) # only regular file in the current dir
    do  echo ${fname##*\.} #print extensions 
done
Albin. Com.
quelle
In Ihrer Antwort wird ein doppelter Backslash verwendet, in Ihrem Beispiel jedoch nur ein einzelner Backslash. Ihr Beispiel ist richtig, Ihre Antwort nicht.
Gilbertpilz
3

Ich habe ein Bash-Skript geschrieben, das den Dateityp untersucht und dann an einen Speicherort kopiert. Ich verwende es, um die Videos zu durchsuchen, die ich online aus meinem Firefox-Cache angesehen habe:

#!/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"&

Es verwendet ähnliche Ideen wie die hier vorgestellten, hoffe, dass dies für jemanden hilfreich ist.

Desdecode
quelle
2

Ich denke das '$PATH_TO_SOMEWHERE'ist so etwas wie '<directory>/*'.

In diesem Fall würde ich den Code ändern in:

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

Wenn Sie mit den Verzeichnis- und Textdateinamen etwas komplizierteres tun möchten, können Sie:

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

Wenn Ihre Dateinamen Leerzeichen enthalten, können Sie:

find <directory> -maxdepth 1 -type d | xargs ...
find <directory> -maxdepth 1 -type f -name "*.txt" | xargs ...
jfg956
quelle
Dies sind großartige Beispiele dafür, wie Sie "Loops" in der Shell ausführen. Explizite forund whileSchleifen sollten besser reserviert werden, wenn der Schleifenkörper komplexer sein muss.