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

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

/the/path/foo.txt
bar.txt

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

foo
bar

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

#!/bin/bash

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

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

Ответ 1

Вам не нужно вызывать внешнюю команду 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

Ответ 2

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

fbname=$(basename "$1" .txt)
echo "$fbname"

Ответ 3

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

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

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

Ответ 4

Чистый bash, без basename, без basename переменных. Установите строку и echo:

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

Выход:

foo

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

shopt -s extglob

Пройдя по ${s//+(*\/|.*)}:

  1. ${s - начать с $ s.
  2. // подставляем каждый экземпляр шаблона.
  3. +( соответствует одному или нескольким спискам шаблонов в круглых скобках (т.е. до позиции № 7 ниже).
  4. 1-й шаблон: *\/ соответствует чему-либо перед буквенным символом " / ".
  5. разделитель шаблонов | который в этом случае действует как логическое ИЛИ.
  6. 2-й шаблон:. .* Соответствует чему-либо после литерала " . ", bash есть в bash " . " - это просто символ точки, а не точка регулярного выражения.
  7. ) конец списка шаблонов.
  8. } расширение параметра end - поскольку нет / (что должно предшествовать подстановке строки), сопоставленные шаблоны заменяются ничем; это удаляет совпадения.

Соответствующий man bash background:

  1. замена шаблона:
  ${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

Ответ 5

Здесь есть oneliners:

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

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

Ответ 6

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

filename=`rev <<< "$1" | cut -d"." -f2- | rev`
fileext=`rev <<< "$1" | cut -d"." -f1 | rev`

Ответ 7

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

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

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

Ответ 8

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

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

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