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

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
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» с помощью теста длины строки и повторить исправление несколько раз.

eadmaster
источник
-1

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

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

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

Урай
источник