Питання Вилучити ім'я файлу та розширення в Bash


Я хочу отримати ім'я файлу (без розширення) та розширення окремо.

Найкраще рішення, яке я отримав до цього часу:

NAME=`echo "$FILE" | cut -d'.' -f1`
EXTENSION=`echo "$FILE" | cut -d'.' -f2`

Це неправильно, оскільки він не працює, якщо ім'я файлу містить декілька . персонажів. Якщо, скажімо, у мене є a.b.js, це буде розглядати a і b.js, замість a.b і js.

Це можна зробити легко в Python

file, ext = os.path.splitext(path)

але я вважаю за краще не запускати інтерпретатора Python саме для цього, якщо це можливо.

Будь-які кращі ідеї?


1620
2018-06-08 14:00


походження


Це питання пояснює цю баш-методику та кілька інших пов'язаних з ними. - jjclarkson
Застосовуючи чудові відповіді нижче, не просто вставте в свою змінну, як я показую тут Неправильно:  extension="{$filename##*.}" як я зробив на деякий час! Перемістити $ за межами кучерків: Праворуч:  extension="${filename##*.}" - Chris K
Це, безумовно, нетривіальна проблема, і для мене важко сказати, чи відповідають ці відповіді правильними. Дивно, що це не вбудована операція в (ba) sh (відповіді, здається, реалізують функцію, використовуючи відповідність шаблону). Я вирішив скористатися Python's os.path.splitext як вище замість ... - Peter Gibson
Погоджено - такий самий коментар. Чи немає компіляції / неможливого компіляції утиліти, яка приймає командні комутатори для безлічі випадків використання цього - наприклад. отримувати розширення замість, або останні n. роздільники або перевірка типу файлу у бінарному заголовку тощо. .? - Alex Hall
Як розширення треба представляти природа файлу, є a магія команда, яка перевіряє файл, щоб божественно передати свою природу і пропозицію стандартне розширення. побачити моя відповідь - F. Hauri


Відповіді:


По-перше, знайдіть ім'я файлу без шляху:

filename=$(basename -- "$fullfile")
extension="${filename##*.}"
filename="${filename%.*}"

Крім того, ви можете зосередити увагу на останньому "/" шляху, а не на "." які повинні працювати, навіть якщо у вас є непередбачувані розширення файлів:

filename="${fullfile##*/}"

2815
2018-06-08 14:05



Перевірити gnu.org/software/bash/manual/html_node/... для повного набору функцій. - D.Shawley
Додати деякі лапки до "$ fullfile", або ви ризикуєте зламати назву файлу. - lhunath
Чорт, ви навіть можете написати filename = "$ {fullfile ## * /}" і не дзвонити додатково basename - ephemient
Цей "розв'язок" не працює, якщо файл не має розширення - замість цього виводиться повне ім'я файлу, що є досить поганим, вважаючи, що файли без розширень є в усьому світі. - nccc
Виправити для роботи з іменами файлів без розширення: extension=$([[ "$filename" = *.* ]] && echo ".${filename##*.}" || echo ''). Зауважте, що якщо розширення є На даний момент він буде повернуто, включаючи початкову ., наприклад, .txt. - mklement0


~% FILE="example.tar.gz"
~% echo "${FILE%%.*}"
example
~% echo "${FILE%.*}"
example.tar
~% echo "${FILE#*.}"
tar.gz
~% echo "${FILE##*.}"
gz

Докладніше див Розширення параметра оболонки в керівництві Bash.


503
2018-06-08 14:05



Ви (можливо, ненавмисне) висвітлюєте відмінне питання про те, що робити, якщо в "extension" частині імені файлу є 2 точки в ньому, як у .tar.gz ... Я ніколи не розглядав цю проблему, і я підозрюю, що це не вирішується, не знаючи всіх можливих правильних розширень файлів вперед. - rmeador
Чому б не вирішити? У моєму прикладі слід вважати, що файл містить два розширення, а не розширення з двома точками. Ви обробляєте обидва розширення окремо. - Juliano
Це неможливо розв'язати на лексичній основі, потрібно перевірити тип файлу. Подумайте, якщо у вас була названа гра dinosaurs.in.tar і ви наклепили його dinosaurs.in.tar.gz :) - porges
Це стає все складніше, якщо ви пройдете повні шляхи. Один з моїх мав '.' у каталозі посередині шляху, але ніхто не має назви файлу. Приклад "a / b.c / d / e / filename" призведе до закінчення ".c / d / e / filename" - Walt Sellers
ясно немає x.tar.gzросійське розширення є gz і ім'я файлу є x.tar це все. Немає таких речей, як подвійні розширення. Я впевнений, що boost :: файлова система дотримується цього. (розділений шлях, change_extension ...), і його поведінка заснована на python, якщо я не помиляюся. - v.oddou


Зазвичай ви вже знаєте розширення, так що ви можете використати:

basename filename .extension

наприклад:

basename /path/to/dir/filename.txt .txt

і ми отримуємо

filename

281
2017-10-19 10:56



Цей другий аргумент до basename це цілком око-відкривач, ти доброго сер / пані :) - akaIDIOT
І як витягти розширення, використовуючи цю техніку? ;) Зачекайте! Ми насправді не знаємо це передчасно. - Tomasz Gandor
Скажімо, у вас є архівний каталог, який закінчується або закінчується .zip або .ZIP. Чи є спосіб зробити щось подібне basename $file {.zip,.ZIP}? - Dennis
Хоча це лише відповідає на частину питання про ОП, він відповідає на питання, яке я ввів у Google. :-) Дуже гладкий! - sudo make install


Ви можете використовувати магію змінних POSIX:

bash-3.2$ FILENAME=somefile.tar.gz
bash-3.2$ echo ${FILENAME%%.*}
somefile
bash-3.2$ echo ${FILENAME%.*}
somefile.tar

У цьому полягає застереження, якщо ваше ім'я файлу має форму ./somefile.tar.gz потім echo ${FILENAME%%.*} буде жадібно зняти найдовший матч з . і у вас буде порожня рядок.

(Ви можете обійти це з тимчасовою змінною:

FULL_FILENAME=$FILENAME
FILENAME=${FULL_FILENAME##*/}
echo ${FILENAME%%.*}

)


Це сайт пояснює більше.

${variable%pattern}
  Trim the shortest match from the end
${variable##pattern}
  Trim the longest match from the beginning
${variable%%pattern}
  Trim the longest match from the end
${variable#pattern}
  Trim the shortest match from the beginning

125
2018-02-05 09:09



Набагато простіше, ніж відповідь Йоакіма, але мені завжди доводиться шукати змінні заміни POSIX. Також це працює на Max OSX де cut не має --complement і sed не має -r. - jwadsack


Це, здається, не працює, якщо файл не має розширення або не містить імені файлу. Ось що я використовую; він використовує тільки builtins та обробляє ще (але не всі) патологічні імена файлів.

#!/bin/bash
for fullpath in "$@"
do
    filename="${fullpath##*/}"                      # Strip longest match of */ from start
    dir="${fullpath:0:${#fullpath} - ${#filename}}" # Substring from 0 thru pos of filename
    base="${filename%.[^.]*}"                       # Strip shortest match of . plus at least one non-dot char from end
    ext="${filename:${#base} + 1}"                  # Substring from len of base thru end
    if [[ -z "$base" && -n "$ext" ]]; then          # If we have an extension and no base, it's really the base
        base=".$ext"
        ext=""
    fi

    echo -e "$fullpath:\n\tdir  = \"$dir\"\n\tbase = \"$base\"\n\text  = \"$ext\""
done

І ось кілька тестів:

$ basename-and-extension.sh / / home / me / / home / me / file /home/me/file.tar/home/me/file.tar.gz /home/me/.hidden / home / me / .hidden.tar / home / me / ...
/:
    dir = "/"
    база = ""
    ext = ""
/ home / me /:
    dir = "/ home / me /"
    база = ""
    ext = ""
/ home / me / file:
    dir = "/ home / me /"
    base = "file"
    ext = ""
/home/me/file.tar:
    dir = "/ home / me /"
    base = "file"
    ext = "tar"
/home/me/file.tar.gz:
    dir = "/ home / me /"
    base = "file.tar"
    ext = "gz"
/home/me/.hidden:
    dir = "/ home / me /"
    base = ". hidden"
    ext = ""
/home/me/.hidden.tar:
    dir = "/ home / me /"
    base = ". hidden"
    ext = "tar"
/ home / me / ..:
    dir = "/ home / me /"
    база = ".."
    ext = ""
.:
    dir = ""
    база = "."
    ext = ""

65
2017-09-10 05:17



Замість dir="${fullpath:0:${#fullpath} - ${#filename}}" Я часто бачив dir="${fullpath%$filename}". Простіше писати. Не впевнений, чи існує будь-яка реальна різниця швидкості або готха. - dubiousjim
Це використовує #! / Bin / bash, який майже завжди неправий. Віддайте перевагу #! / Bin / sh, якщо це можливо, або #! / Usr / bin / env bash, якщо ні. - Good Person
@ Доброго людини: Я не знаю, як це майже завжди не так: which bash -> /bin/bash ; можливо, це ваш дистрибутив? - vol7ron
@ vol7ron - на багатьох дистрибутивах bash знаходиться в / usr / local / bin / bash. У OSX багато людей встановлюють оновлену башу в / opt / local / bin / bash. Оскільки такий / bin / bash неправильний і потрібно використовувати env, щоб знайти його. Ще краще використовувати конструкції / bin / sh і POSIX. За винятком solaris це оболонка POSIX. - Good Person
@GoodPerson, але якщо ви більш комфортно з bash, чому б використовувати sh? Чи не так, як кажуть, чому використовувати Perl, коли ви можете використовувати sh? - vol7ron


Ви можете використовувати basename.

Приклад:

$ basename foo-bar.tar.gz .tar.gz
foo-bar

Вам потрібно надати базова назва з розширенням, яке буде видалено, однак, якщо ви завжди виконуєте tar з -z тоді ви знаєте, що розширення буде .tar.gz.

Це має робити те, що ви хочете:

tar -zxvf $1
cd $(basename $1 .tar.gz)

40
2018-02-05 08:50



Я вважаю cd $(basename $1 .tar.gz) працює для файлів .gz. Але в питанні він згадав Archive files have several extensions: tar.gz, tat.xz, tar.bz2 - SS Hegde
Томі По опублікував ті самі речі 2 роки тому. - Blauhirn
Привіт, Блаухінн, вибачте це старі питання. Я думаю щось сталося з датами. Я чітко пам'ятаю, відповідаючи на це питання незабаром після того, як його попросили, і там, де тільки пару інших відповідей. Чи може це бути, що питання було об'єднано з іншим, чи так робить це? - Bjarke Freund-Hansen
Так, я правильно пам'ятаю. Я спочатку відповідає на це питання stackoverflow.com/questions/14703318/... в той же день це було задано, через 2 роки вона була об'єднана в цю. Мені навряд чи можна звинувачувати дублікат відповіді, коли моя відповідь була перенесена таким чином. - Bjarke Freund-Hansen


Меллен пише у коментарі до блогу:

Використання Bash також існує ${file%.*} щоб отримати ім'я файлу без розширення і ${file##*.} одержувати розширення окремо. Це,

file="thisfile.txt"
echo "filename: ${file%.*}"
echo "extension: ${file##*.}"

Виходи:

filename: thisfile
extension: txt

24
2017-07-21 10:24



Як саме цей синтаксис працює? - syntagma
@ REACHUS: Див gnu.org/software/bash/manual/html_node/... - mklement0