Извлечь базовое имя файла без пути и расширения в bash

203

Указанные имена файлов, такие как:

/the/path/foo.txt
bar.txt

Я надеюсь получить:

foo
bar

Почему это не работает?

#!/bin/bash

fullfile=$1
fname=$(basename $fullfile)
fbname=${fname%.*}
echo $fbname

Какой правильный способ сделать это?

  • 1
    Этот код должен работать в большинстве случаев до тех пор, пока значение $1 на самом деле то, что вы думаете. Тем не менее, он может быть разделен по словам и расширению имени файла из-за неправильного цитирования .
Теги:
filenames

9 ответов

399
Лучший ответ

Вам не нужно вызывать внешнюю команду basename. Вместо этого вы можете использовать следующие команды:

$ s=/the/path/foo.txt
$ echo ${s##*/}
foo.txt
$ s=${s##*/}
$ echo ${s%.txt}
foo
$ echo ${s%.*}
foo

Обратите внимание, что это решение должно работать во всех недавних (post 2004) POSIX-совместимых оболочках (например, bash, dash, ksh и т.д.).

Источник: Командный язык оболочки 2.6.2 Расширение параметров

Подробнее о bash Строковые манипуляции: http://tldp.org/LDP/LG/issue18/bash.html

  • 48
    фантастический ответ ... Манипуляции с bash-строками (например, ${s##*/} ) описаны здесь linuxgazette.net/18/bash.html
  • 6
    @chim вы нашли обновленную ссылку на вашу ссылку? Он мертв.
Показать ещё 13 комментариев
205

Команда basename имеет два разных вызова; в одном вы указываете только путь, и в этом случае он дает вам последний компонент, в то время как в другом вы также указываете суффикс, который он удалит. Таким образом, вы можете упростить код примера, используя второй вызов basename. Кроме того, будьте осторожны, чтобы правильно процитировать вещи:

fbname=$(basename "$1" .txt)
echo "$fbname"
  • 8
    Есть ли способ заставить его удалить любой суффикс?
  • 1
    @handuel К сожалению, basename не поддерживает подстановочные знаки. Предоставление второго аргумента удалит только эту точную буквальную строку с конца.
Показать ещё 2 комментария
39

Комбинация basename и cut работает отлично, даже в случае двойного окончания, например .tar.gz:

fbname=$(basename "$fullfile" | cut -d. -f1)

Было бы интересно, если это решение нуждается в меньшей арифметической мощности, чем Bash Расширение параметров.

  • 2
    Это мой предпочтительный способ - с незначительным изменением использования $(..) - так что это становится: fbname=$(basename "$fullfile" | cut -d. -f1)
  • 0
    Это хорошее решение в том, что оно снимет все (точечные) расширения.
Показать ещё 1 комментарий
8

Чистое bash, no basename, переменное жонглирование. Задайте строку и echo:

s=/the/path/foo.txt
echo ${s//+(*\/|.*)}

Вывод:

foo

Примечание: параметр bash extglob должен быть "on", (по умолчанию Ubuntu it "on"), если это не так:

shopt -s extglob

Прогулка по ${s//+(*\/|.*)}:

  • ${s - начать с $s.
  • // заменить каждый экземпляр шаблона.
  • +( соответствуют одному или нескольким спискам шаблонов в скобках.
  • *\/ соответствует чему-либо перед /. (1-й шаблон)
  • | Или. (разделитель рисунков.)
  • .* соответствует чем-либо после .. (2-й шаблон)
  • ) список концевых шаблонов.
  • } расширение концевого параметра - поскольку там нет / (который должен предшествовать замене строки), сопоставленные шаблоны удаляются.

Релевантный man bash фон:

  • замена шаблона:
  ${parameter/pattern/string}
          Pattern substitution.  The pattern is expanded to produce a pat‐
          tern just as in pathname expansion.  Parameter is  expanded  and
          the  longest match of pattern against its value is replaced with
          string.  If pattern begins with /, all matches  of  pattern  are
          replaced   with  string.   Normally  only  the  first  match  is
          replaced.  If pattern begins with #, it must match at the begin‐
          ning of the expanded value of parameter.  If pattern begins with
          %, it must match at the end of the expanded value of  parameter.
          If string is null, matches of pattern are deleted and the / fol‐
          lowing pattern may be omitted.  If parameter is @ or *, the sub‐
          stitution  operation  is applied to each positional parameter in
          turn, and the expansion is the resultant list.  If parameter  is
          an  array  variable  subscripted  with  @ or *, the substitution
          operation is applied to each member of the array  in  turn,  and
          the expansion is the resultant list.
  1. расширенный шаблон соответствия:
  If the extglob shell option is enabled using the shopt builtin, several
   extended  pattern  matching operators are recognized.  In the following
   description, a pattern-list is a list of one or more patterns separated
   by a |.  Composite patterns may be formed using one or more of the fol‐
   lowing sub-patterns:

          ?(pattern-list)
                 Matches zero or one occurrence of the given patterns
          *(pattern-list)
                 Matches zero or more occurrences of the given patterns
          +(pattern-list)
                 Matches one or more occurrences of the given patterns
          @(pattern-list)
                 Matches one of the given patterns
          !(pattern-list)
                 Matches anything except one of the given patterns
7

Вот еще один (более сложный) способ получения имени файла или расширения, сначала используйте команду rev для инвертирования пути к файлу, вырезанного из первого ., а затем снова инвертируйте путь к файлу, например:

filename=`rev <<< "$1" | cut -d"." -f2- | rev`
fileext=`rev <<< "$1" | cut -d"." -f1 | rev`
  • 0
    Я никогда не видел эти тройные угловые скобки <<< doohickeys - что это?
  • 0
    Они называются «Здесь строки» (более подробная информация здесь ), в основном он принимает входные данные в виде строки и передает их в вашу программу во время чтения через stdin.
5

Здесь есть oneliners:

  • $(basename ${s%.*})
  • $(basename ${s} .${s##*.})

Мне это нужно, так же, как попросили bongbang и w4etwetewtwet.

2

Если вы хотите хорошо играть с файловыми путями Windows (под Cygwin), вы также можете попробовать следующее:

fname=${fullfile##*[/|\\]}

Это будет учитывать разделители обратной косой черты при использовании BaSH в Windows.

0

Просто альтернатива, которую я придумал, чтобы извлечь расширение, используя сообщения в этом потоке с моей собственной небольшой базой знаний, которая была мне более знакома.

ext="$(rev <<< "$(cut -f "1" -d "." <<< "$(rev <<< "file.docx")")")"

Примечание. Просьба сообщить о моем использовании котировок; это сработало для меня, но я мог бы что-то упустить при их правильном использовании (я, вероятно, использую слишком много).

  • 0
    Вам не нужна ни одна кавычка, вы пытаетесь сделать то же самое, что и: rev <<< file.docx | cut -f1 -d. | rev просто без кавычек и без вложенных вложенных оболочек. Также я не думаю, что вышеупомянутое может работать вообще.
-9

Используйте команду basename. Его manpage здесь: http://unixhelp.ed.ac.uk/CGI/man-cgi?basename

  • 7
    Он использует basename , но это не его проблема.

Ещё вопросы

Сообщество Overcoder
Наверх
Меню