Jak sprawdzić rozszerzenie nazwy pliku w skrypcie bash?

169

Piszę skrypt kompilacji na noc w bashu.
Wszystko jest w porządku i eleganckie, z wyjątkiem jednej małej przeszkody:


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

Mój problem polega na określeniu rozszerzenia pliku i odpowiednim działaniu. Wiem, że problem tkwi w instrukcji if, która testuje plik txt.

Jak mogę sprawdzić, czy plik ma rozszerzenie .txt?

Anthon
źródło
To się zepsuje, jeśli masz plik ze spacją w nazwie.
jfg956
Oprócz odpowiedzi Paula możesz użyć $(dirname $PATH_TO_SOMEWHERE)i $(basename $PATH_TO_SOMEWHERE)podzielić na folder i katalog i zrobić coś katalog-ish i plik-ish
McPeppr

Odpowiedzi:

248

Myślę, że chcesz powiedzieć „Czy ostatnie cztery znaki $ file są równe .txt?” Jeśli tak, możesz skorzystać z:

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

Zwróć uwagę, że odstęp między file:i -4jest wymagany, ponieważ modyfikator „: -” oznacza coś innego.

Paul Stephenson
źródło
1
w tym celu możesz zmienić nazwę command.com na command.txt również na komputerze z systemem Windows.
hometoast
9
Jeśli chcesz określić nierówność, pamiętaj o
dodaniu
4
@RamRajamony Dlaczego konieczne jest użycie [[podczas testowania nierówności?
PesaThe
Chciałem zwrócić uwagę, że przestrzeń za okrężnicą jest ważna. ${var:-4}nie jest tym samym, co ${var: -4}; pierwsza (bez spacji) rozwinie się jako „-4”, jeśli zmienna nie jest ustawiona, druga (ze spacją) zwraca ostatnie 4 znaki zmiennej.
pbatey
1
W bashu spowoduje to błąd „[: ==: oczekiwano jednoargumentowego operatora”, chyba że umieścisz cudzysłowy wokół pierwszej zmiennej. Więc if [ "${file: -4}" == ".txt" ]zamiast tego.
Giles B
264

Robić

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

lubię to:

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

To znaczy, podwójne nawiasy i brak cudzysłowów.

Prawa strona ==to wzór muszli. Jeśli potrzebujesz wyrażenia regularnego, użyj =~wtedy.


źródło
15
Nie wiedziałem o tym. Wydaje się, że jest to szczególny przypadek, gdy prawa strona == lub! = Jest rozszerzana jako wzorzec powłoki. Osobiście uważam, że to jest jaśniejsze niż moja odpowiedź.
Paul Stephenson
20
Jestem nowy w bash i zajęło mi trochę czasu, zanim wymyśliłem, jak tego użyć w ifinstrukcji wielowarstwowej . Udostępniam to tutaj na wypadek, gdyby to komuś pomogło. if [[ ( $file == *.csv ) || ( $file == *.png ) ]]
joelostblom
6
@cheflo jest to ogólnie dobre dla wielu warunków. W tym konkretnym przypadku możesz również użyć if [[ $file =~ .*\.(csv|png) ]]. Jest krótszy, bardziej przejrzysty, łatwiejszy do dodania dodatkowych rozszerzeń i może być łatwo konfigurowalny (poprzez umieszczenie "csv | png" w zmiennej).
jox
4
Możesz umieścić plik w cudzysłowie. if [[ "$file" == *.txt ]]Jeśli plik zawiera spacje w nazwie, wymagane jest umieszczanie w cudzysłowie.
shawnhcorey
Czy powinienem użyć tej odpowiedzi zamiast zaakceptowanej?
Freedo
26

Po prostu nie możesz być pewien w systemie Unix, że plik .txt naprawdę jest plikiem tekstowym. Najlepszym rozwiązaniem jest użycie „pliku”. Może spróbuj użyć:

file -ib "$file"

Następnie możesz użyć listy typów MIME, aby dopasować lub przeanalizować pierwszą część MIME, w której otrzymujesz takie rzeczy, jak „tekst”, „aplikacja” itp.

Eldelshell
źródło
2
Jako file -i...obejmuje kodowanie MIME, można użyćfile --mime-type -b ...
fotograf Wilf
24

Możesz użyć polecenia „plik”, jeśli faktycznie chcesz uzyskać informacje o pliku, zamiast polegać na rozszerzeniach.

Jeśli czujesz się komfortowo z używaniem rozszerzenia, możesz użyć grep, aby sprawdzić, czy pasuje.

Adam Peck
źródło
tak, znam filepolecenie. Właściwie próbowałem dopasować na podstawie danych wyjściowych wspomnianego polecenia ... ale strasznie mi się nie udaje.
17

Możesz też:

   if [ "${FILE##*.}" = "txt" ]; then
       # operation for txt files here
   fi
kvz
źródło
11
case $FILE in *.txt ) ... ;; esacwydawałoby się solidniejsze i bardziej idiomatyczne.
tripleee
11

Podobnie jak „plik”, użyj nieco prostszego „mimetype -b”, które będzie działać bez względu na rozszerzenie pliku.

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

Edycja: może być konieczne zainstalowanie libfile-mimeinfo-perl w systemie, jeśli typ MIME nie jest dostępny

dargaud
źródło
1
Należy wyjaśnić, że skrypt „MIME” nie jest dostępny we wszystkich systemach.
gilbertpilz
Gotowe, dodano libfile-mimeinfo-perl
dargaud
5

Prawidłowa odpowiedź na pytanie, jak wziąć rozszerzenie dostępne w nazwie pliku w systemie Linux, to:

${filename##*\.} 

Przykład drukowania wszystkich rozszerzeń plików w katalogu

for fname in $(find . -maxdepth 1 -type f) # only regular file in the current dir
    do  echo ${fname##*\.} #print extensions 
done
Albin. Com.
źródło
Twoja odpowiedź używa podwójnego odwrotnego ukośnika, ale w Twoim przykładzie używany jest tylko jeden lewy ukośnik. Twój przykład jest poprawny, twoja odpowiedź nie.
gilbertpilz
3

Napisałem skrypt bash, który sprawdza typ pliku, a następnie kopiuje go do lokalizacji, używam go do przeglądania filmów, które oglądałem online z pamięci podręcznej Firefoksa:

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

Korzysta z pomysłów podobnych do przedstawionych tutaj, mam nadzieję, że komuś to pomoże.

desdecode
źródło
2

Myślę, że to '$PATH_TO_SOMEWHERE'jest coś takiego '<directory>/*'.

W takim przypadku zmieniłbym kod na:

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

Jeśli chcesz zrobić coś bardziej skomplikowanego z nazwami katalogów i plików tekstowych, możesz:

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

Jeśli masz spacje w nazwach plików, możesz:

find <directory> -maxdepth 1 -type d | xargs ...
find <directory> -maxdepth 1 -type f -name "*.txt" | xargs ...
jfg956
źródło
To są świetne przykłady tego, jak robisz „pętle” w powłoce. Jawne fori whilepętle lepiej być zarezerwowane, gdy treść pętli musi być bardziej złożona.