Захоплення розширення в імені файлу


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
file = "/ dir / subdir / file-1.0.tar.bz2"; echo $ {file ## *.} друкує тут '.bz2'. Який результат ви очікуєте?
axel_c

1
мені потрібно.tar.bz2
uray

Відповіді:


19

Якщо ім'я файлу є file-1.0.tar.bz2, розширення є bz2. Метод, який ви використовуєте для витягування розширення ( 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_globunder 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має закінчитися його тип архіву, як ітерація.
uray

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;')}"
Жил 'SO- перестаньте бути зла'

@axel_c: вірно, і я застосував таку ж специфікацію, що і Maciej як приклад. Яке евристичне ви вважаєте, що краще, ніж "починається з літери"?
Жил "ТАК - перестань бути злим"

1
@Gilles: Я просто думаю, що немає рішення, якщо ви не використовуєте попередньо розрахований список відомих розширень, оскільки розширення може бути будь-яким.
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" за допомогою тесту на довжину рядка і повторити виправлення кілька разів.


-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.