Получение расширения в имени файла


33

Как мне получить расширение файла от bash? Вот что я попробовал:

filename=`basename $filepath`
fileext=${filename##*.}

Делая это, я могу получить расширение bz2от пути /dir/subdir/file.bz2, но у меня есть проблема с путем /dir/subdir/file-1.0.tar.bz2.

Я бы предпочел решение, использующее только bash без внешних программ, если это возможно.

Чтобы прояснить мой вопрос, я создавал bash-скрипт для извлечения любого данного архива только одной командой extract path_to_file. Как извлечь файл, определяется сценарием, видя его тип сжатия или архивирования, который может быть .tar.gz, .gz, .bz2 и т. Д. Я думаю, что это должно включать в себя манипуляции со строками, например, если я получаю расширение, .gzто я должен проверить, есть ли у него строка .tarраньше .gz- если это так, расширение должно быть .tar.gz.


2
Файл = "/ реж / подкаталог / файлов 1.0.tar.bz2"; echo $ {file ## *.} печатает здесь .bz2. Какой выход вы ожидаете?
axel_c

1
.tar.bz2
Мне

Ответы:


19

Если имя файла - file-1.0.tar.bz2расширение bz2. Метод, который вы используете для извлечения extension ( fileext=${filename##*.}), абсолютно корректен¹.

Как вы решаете, что вы хотите, чтобы расширение было, tar.bz2а не bz2или 0.tar.bz2? Вы должны ответить на этот вопрос в первую очередь. Затем вы можете выяснить, какая команда оболочки соответствует вашей спецификации.

  • Одна из возможных спецификаций состоит в том, что расширения должны начинаться с буквы. Эта эвристика не работает для нескольких распространенных расширений 7z, которые лучше всего рассматривать как особый случай. Вот реализация bash / ksh / zsh:

    basename=$filename; fileext=
    while [[ $basename = ?*.* &&
             ( ${basename##*.} = [A-Za-z]* || ${basename##*.} = 7z ) ]]
    do
      fileext=${basename##*.}.$fileext
      basename=${basename%.*}
    done
    fileext=${fileext%.}

    Для переносимости POSIX вам нужно использовать caseоператор для сопоставления с образцом.

    while case $basename in
            ?*.*) case ${basename##*.} in [A-Za-z]*|7z) true;; *) false;; esac;;
            *) false;;
          esac
    do 
  • Другая возможная спецификация заключается в том, что некоторые расширения обозначают кодировки и указывают на необходимость дальнейшего удаления. Вот реализация bash / ksh / zsh (требуется shopt -s extglobпод bash и setopt ksh_globпод zsh):

    basename=$filename
    fileext=
    while [[ $basename = ?*.@(bz2|gz|lzma) ]]; do
      fileext=${basename##*.}.$fileext
      basename=${basename%.*}
    done
    if [[ $basename = ?*.* ]]; then
      fileext=${basename##*.}.$fileext
      basename=${basename%.*}
    fi
    fileext=${fileext%.}

    Обратите внимание, что это считается 0расширением в file-1.0.gz.

¹ и связанные с ними конструкции находятся в POSIX , поэтому они работают в любой античной оболочке в стиле Борна, такой как ash, bash, ksh или zsh. ${VARIABLE##SUFFIX}


это должно быть решено путем проверки, если строка перед последним .токеном имеет тип архива, например tar, 0должен ли конец ее не тип архива, такой как итерация.
Урай

2
@uray: это работает в данном конкретном случае, но это не общее решение. Рассмотрим пример Мачей.patch.lzma . Лучше эвристический будет рассматривать строку после последнего .: если это суффикс сжатия ( .7z, .bz2, .gz...), продолжают зачистки.
Жиль "ТАК - перестань быть злым"

@NoamM Что не так с отступом? После вашего редактирования он определенно не работает: код с двумя вложенными кодами имеет такой же отступ, как и одиночный.
Жиль "ТАК - прекрати быть злым"

22

Вы можете упростить ситуацию, просто выполнив сопоставление с шаблоном в имени файла, а не извлекая расширение дважды:

case "$filename" in
    *.tar.bz2) bunzip_then_untar ;;
    *.bz2)     bunzip_only ;;
    *.tar.gz)  untar_with -z ;;
    *.tgz)     untar_with -z ;;
    *.gz)      gunzip_only ;;
    *.zip)     unzip ;;
    *.7z)      do something ;;
    *)         do nothing ;;
esac

Это решение красиво просто.
AsymLabs

6
$ echo "thisfile.txt"|awk -F . '{print $NF}'

Комментарии к этому здесь: http://liquidat.wordpress.com/2007/09/29/short-tip-get-file-extension-in-shell-script/


1
не работает для .tar.gzпродления
Uray

4
Ну, на самом деле .tar.gz - это tar внутри gzip-файла, поэтому он работает в том смысле, что удаляет расширение gz из gzip-файла.
Крис

2

Вот мой пример: переведите точки в новые строки, пролистайте tail, получите последнюю строку:

$> TEXT=123.234.345.456.456.567.678
$> echo $TEXT | tr . \\n | tail -n1
678

0
echo ${filename#$(echo $filename | sed 's/\.[^[:digit:]].*$//g;')}

Например:

% echo $filename
2.6.35-zen2.patch.lzma
% echo ${filename#$(echo $filename | sed 's/\.[^[:digit:]].*$//g;')}
.patch.lzma

Не работает для всех случаев. Попробуйте с 'foo.7z'
axel_c

Вам нужны кавычки, и лучше их использовать printfв том случае, если имя файла содержит обратную косую черту или начинается с -:"${filename#$(printf %s "$filename" | sed 's/\.[^[:digit:]].*$//g;')}"
Жиль "ТАК - перестать быть злым"

@axel_c: верно, и я реализовал ту же спецификацию, что и Maciej в качестве примера. Какую эвристику вы считаете лучше, чем «начинается с буквы»?
Жиль "ТАК - перестать быть злым"

1
@ Жиль: я просто думаю, что нет решения, если вы не используете предварительно вычисленный список известных расширений, потому что расширение может быть чем угодно.
axel_c

0

Однажды я создал эти хитрые функции:

# args: string how_many
function get_last_letters(){ echo ${1:${#1}-$2:$2}; }
function cut_last_letters(){ echo ${1:0:${#1}-$2}; }

Я нашел этот простой подход, очень полезный во многих случаях, не только когда речь идет о расширениях.

Для проверки расширений - это просто и надежно

~$ get_last_letters file.bz2 4
.bz2
~$ get_last_letters file.0.tar.bz2 4
.bz2

Для отсечки расширения:

~$ cut_last_letters file.0.tar.bz2 4
file.0.tar

Для изменения добавочного номера:

~$ echo $(cut_last_letters file.0.tar.bz2 4).gz
file.0.tar.gz

Или, если вам нравятся «удобные функции:

~$ function cut_last_letters_and_add(){ echo ${1:0:${#1}-$2}"$3"; }
~$ cut_last_letters_and_add file.0.tar.bz2 4 .gz
file.0.tar.gz

PS Если вам понравились эти функции или вы нашли их использованными полностью, пожалуйста, обратитесь к этому посту :) (и, надеюсь, оставьте комментарий).


0

Ответ Джекмана, основанный на регистре, довольно хороший и переносимый, но если вы просто хотите указать имя файла и расширение в переменной, я нашел это решение:

INPUTFILE="$1"
INPUTFILEEXT=$( echo -n "$INPUTFILE" | rev | cut -d'.' -f1 | rev )
INPUTFILEEXT=$( echo -n $INPUTFILEEXT | tr '[A-Z]' '[a-z]' ) # force lowercase extension
INPUTFILENAME="`echo -n \"$INPUTFILE\" | rev | cut -d'.' -f2- | rev`"

# fix for files with multiple extensions like "gbamidi-v1.0.tar.gz"
INPUTFILEEXT2=$( echo -n "$INPUTFILENAME" | rev | cut -d'.' -f1 | rev )
if [ "$INPUTFILEEXT2" = "tar" ]; then
    # concatenate the extension
    INPUTFILEEXT="$INPUTFILEEXT2.$INPUTFILEEXT"
    # update the filename
    INPUTFILENAME="`echo -n \"$INPUTFILENAME\" | rev | cut -d'.' -f2- | rev`"
fi

Он работает только с двойными расширениями, и первым должно быть «tar».

Но вы можете изменить тестовую строку «tar» с помощью теста длины строки и повторить исправление несколько раз.


-1

я решил это с помощью этого:

filename=`basename $filepath`
fileext=${filename##*.}
fileext2=${filename%.*}
fileext3=${fileext2##*.}
if [ "$fileext3" == "tar" ]; then
    fileext="tar."$fileext
fi

но это работает только для известного типа архивации, в данном случае только tar

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.