Как проверить расширение имени файла в скрипте bash?

170

Я пишу ночной сценарий сборки в Bash.
Все хорошо и модно, за исключением одной маленькой загвоздки:


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

Моя проблема заключается в определении расширения файла, а затем действовать соответствующим образом. Я знаю, что проблема в операторе if, проверяющем txt-файл.

Как определить, имеет ли файл суффикс .txt?

Энтон
источник
Это сломается, если у вас есть файл с пробелом в имени.
jfg956
В дополнение к ответу Пола, вы можете использовать $(dirname $PATH_TO_SOMEWHERE)и $(basename $PATH_TO_SOMEWHERE)для разделения на папки и директории и делать что-то с каталогами и файлами
McPeppr

Ответы:

250

Я думаю, что вы хотите сказать: «Последние четыре символа $ file равны .txt?» Если это так, вы можете использовать следующее:

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

Обратите внимание, что между file:и -4требуется пробел , так как модификатор ': -' означает что-то другое.

Пол Стивенсон
источник
1
для этого вы также можете переименовать command.com в command.txt на машине с Windows.
Hometoast
9
Если вы хотите указать неравенство, не забудьте включить дополнительные скобки: if [[$ {file: -4}! = ".Txt"]]
Рам Раджамони,
4
@RamRajamony Почему необходимо использовать [[при проверке неравенства?
PesaThe
Я хотел указать, что пространство после двоеточия важно. ${var:-4}это не то же самое, что ${var: -4}; первый (без пробела) будет расширяться как '-4', если переменная не установлена, вторая (с пробелом) возвращает последние 4 символа переменной var.
pbatey
1
В bash это приведет к ошибке «[: ==: ожидается унарный оператор», если вы не поместите кавычки вокруг первой переменной. Так что if [ "${file: -4}" == ".txt" ]вместо.
Джайлз Б
264

Делать

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

как это:

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

То есть в двойных скобках и без кавычек.

Правая сторона ==- это шаблон оболочки. Если вам нужно регулярное выражение, используйте =~тогда.


источник
15
Я не знал об этом. Кажется, это особый случай, когда правая часть == или! = Раскрывается как шаблон оболочки. Лично я думаю, что это яснее, чем мой ответ.
Пол Стивенсон
20
Я новичок в bash, и мне потребовалось немного времени, чтобы понять, как использовать это в условном ifвыражении. Я делюсь этим здесь на случай, если это кому-то поможет. if [[ ( $file == *.csv ) || ( $file == *.png ) ]]
Joelostblom
6
@cheflo это хорошо для нескольких условий в целом. В этом конкретном случае вы также можете использовать if [[ $file =~ .*\.(csv|png) ]]. Он короче, понятнее, проще добавлять дополнительные расширения и его можно легко настроить (поместив «csv | png» в переменную).
Jox
4
Вы можете поместить двойные кавычки вокруг файла. if [[ "$file" == *.txt ]]Если в имени файла есть пробелы, двойные кавычки обязательны.
shawnhcorey
Должен ли я использовать этот ответ вместо принятого?
Фридо
26

Вы просто не можете быть уверены в системе Unix, что файл .txt действительно является текстовым файлом. Лучше всего использовать файл. Может быть, попробуйте использовать:

file -ib "$file"

Затем вы можете использовать список типов MIME для сопоставления или анализа первой части MIME, где вы получаете такие вещи, как «текст», «приложение» и т. Д.

Eldelshell
источник
2
В том file -i...числе кодирование MIME, вы можете использоватьfile --mime-type -b ...
Уилф
24

Вы можете использовать команду «file», если вы действительно хотите узнать информацию о файле, а не полагаться на расширения.

Если вы чувствуете себя комфортно при использовании расширения, вы можете использовать grep, чтобы увидеть, соответствует ли оно.

Адам Пек
источник
да, я в курсе fileкоманды. Я на самом деле пытался сопоставить, основываясь на выводе указанной команды ... но я ужасно провалился в этих операторах if.
17

Вы также можете сделать:

   if [ "${FILE##*.}" = "txt" ]; then
       # operation for txt files here
   fi
КВЗ
источник
11
case $FILE in *.txt ) ... ;; esacказалось бы более надежным и идиоматическим.
tripleee
11

Подобно «file», используйте чуть более простой «mimetype -b», который будет работать независимо от расширения файла.

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

Редактировать: вам может понадобиться установить libfile-mimeinfo-perl в вашей системе, если mimetype недоступен

dargaud
источник
1
Вы должны четко дать понять, что скрипт mimetype доступен не во всех системах.
Гилбертпилз
Готово, добавлен libfile-mimeinfo-perl
dargaud
5

Правильный ответ о том, как получить расширение, доступное в имени файла в linux:

${filename##*\.} 

Пример печати всех расширений файлов в каталоге

for fname in $(find . -maxdepth 1 -type f) # only regular file in the current dir
    do  echo ${fname##*\.} #print extensions 
done
Альбин. Com.
источник
В вашем ответе используется двойной обратный слеш, но в вашем примере используется только один обратный слеш. Ваш пример правильный, ваш ответ - нет.
Гилбертпилз
3

Я написал bash-скрипт, который просматривает тип файла, затем копирует его в папку, и я использую его для просмотра видео, которые я смотрел онлайн, из моего кеша 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"&

Он использует идеи, аналогичные представленным здесь, надеюсь, это кому-нибудь пригодится.

desdecode
источник
2

Я думаю, '$PATH_TO_SOMEWHERE'это что-то вроде '<directory>/*'.

В этом случае я бы изменил код на:

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

Если вы хотите сделать что-то более сложное с именами каталогов и текстовых файлов, вы можете:

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

Если в именах файлов есть пробелы, вы можете:

find <directory> -maxdepth 1 -type d | xargs ...
find <directory> -maxdepth 1 -type f -name "*.txt" | xargs ...
jfg956
источник
Это отличные примеры того, как вы делаете «циклы» в оболочке. Явные forи whileциклы лучше зарезервировать для случаев, когда тело цикла должно быть более сложным.