Извлечь имя файла и расширение в Bash - PullRequest
1849 голосов
/ 08 июня 2009

Я хочу получить имя файла (без расширения) и расширение отдельно.

Лучшее решение, которое я нашел на данный момент:

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 только для этого, если это возможно.

Есть идеи получше?

Ответы [ 37 ]

20 голосов
/ 16 июня 2015

Вот несколько альтернативных предложений (в основном в awk), включая некоторые расширенные варианты использования, такие как извлечение номеров версий для пакетов программного обеспечения.

f='/path/to/complex/file.1.0.1.tar.gz'

# Filename : 'file.1.0.x.tar.gz'
    echo "$f" | awk -F'/' '{print $NF}'

# Extension (last): 'gz'
    echo "$f" | awk -F'[.]' '{print $NF}'

# Extension (all) : '1.0.1.tar.gz'
    echo "$f" | awk '{sub(/[^.]*[.]/, "", $0)} 1'

# Extension (last-2): 'tar.gz'
    echo "$f" | awk -F'[.]' '{print $(NF-1)"."$NF}'

# Basename : 'file'
    echo "$f" | awk '{gsub(/.*[/]|[.].*/, "", $0)} 1'

# Basename-extended : 'file.1.0.1.tar'
    echo "$f" | awk '{gsub(/.*[/]|[.]{1}[^.]+$/, "", $0)} 1'

# Path : '/path/to/complex/'
    echo "$f" | awk '{match($0, /.*[/]/, a); print a[0]}'
    # or 
    echo "$f" | grep -Eo '.*[/]'

# Folder (containing the file) : 'complex'
    echo "$f" | awk -F'/' '{$1=""; print $(NF-1)}'

# Version : '1.0.1'
    # Defined as 'number.number' or 'number.number.number'
    echo "$f" | grep -Eo '[0-9]+[.]+[0-9]+[.]?[0-9]?'

    # Version - major : '1'
    echo "$f" | grep -Eo '[0-9]+[.]+[0-9]+[.]?[0-9]?' | cut -d. -f1

    # Version - minor : '0'
    echo "$f" | grep -Eo '[0-9]+[.]+[0-9]+[.]?[0-9]?' | cut -d. -f2

    # Version - patch : '1'
    echo "$f" | grep -Eo '[0-9]+[.]+[0-9]+[.]?[0-9]?' | cut -d. -f3

# All Components : "path to complex file 1 0 1 tar gz"
    echo "$f" | awk -F'[/.]' '{$1=""; print $0}'

# Is absolute : True (exit-code : 0)
    # Return true if it is an absolute path (starting with '/' or '~/'
    echo "$f" | grep -q '^[/]\|^~/'

Во всех случаях использования в качестве входных данных используется исходный полный путь, не зависящий от промежуточных результатов.

16 голосов
/ 22 апреля 2017

Самое маленькое и простое решение (в одну строку):

$ file=/blaabla/bla/blah/foo.txt
echo $(basename ${file%.*}) # foo
14 голосов
/ 09 августа 2013

Принятый ответ хорошо работает в типичных случаях , но не удается в edge случаях , а именно:

  • Для имен файлов без расширения (называемых суффиксом в оставшейся части этого ответа), extension=${filename##*.} возвращает входное имя файла, а не пустую строку.
  • extension=${filename##*.} не включает начальный ., что противоречит соглашению.
    • Слепое добавление . не будет работать для имен файлов без суффикса.
  • filename="${filename%.*}" будет пустой строкой, если имя входного файла начинается с . и не содержит дальнейших символов . (например, .bash_profile) - вопреки соглашению.

---------

Таким образом, сложность надежного решения, охватывающего все граничные случаи требует функции - см. Ее определение ниже; он может вернуть все компоненты пути .

Пример вызова:

splitPath '/etc/bash.bashrc' dir fname fnameroot suffix
# -> $dir == '/etc'
# -> $fname == 'bash.bashrc'
# -> $fnameroot == 'bash'
# -> $suffix == '.bashrc'

Обратите внимание, что аргументы после входного пути выбираются свободно, позиционная переменная names .
Чтобы пропустить не представляющие интереса переменные, которые предшествуют тем, которые есть, укажите _ (для использования одноразовой переменной $_) или ''; например, чтобы извлечь только корень и расширение имени файла, используйте splitPath '/etc/bash.bashrc' _ _ fnameroot extension.


# SYNOPSIS
#   splitPath path varDirname [varBasename [varBasenameRoot [varSuffix]]] 
# DESCRIPTION
#   Splits the specified input path into its components and returns them by assigning
#   them to variables with the specified *names*.
#   Specify '' or throw-away variable _ to skip earlier variables, if necessary.
#   The filename suffix, if any, always starts with '.' - only the *last*
#   '.'-prefixed token is reported as the suffix.
#   As with `dirname`, varDirname will report '.' (current dir) for input paths
#   that are mere filenames, and '/' for the root dir.
#   As with `dirname` and `basename`, a trailing '/' in the input path is ignored.
#   A '.' as the very first char. of a filename is NOT considered the beginning
#   of a filename suffix.
# EXAMPLE
#   splitPath '/home/jdoe/readme.txt' parentpath fname fnameroot suffix
#   echo "$parentpath" # -> '/home/jdoe'
#   echo "$fname" # -> 'readme.txt'
#   echo "$fnameroot" # -> 'readme'
#   echo "$suffix" # -> '.txt'
#   ---
#   splitPath '/home/jdoe/readme.txt' _ _ fnameroot
#   echo "$fnameroot" # -> 'readme'  
splitPath() {
  local _sp_dirname= _sp_basename= _sp_basename_root= _sp_suffix=
    # simple argument validation
  (( $# >= 2 )) || { echo "$FUNCNAME: ERROR: Specify an input path and at least 1 output variable name." >&2; exit 2; }
    # extract dirname (parent path) and basename (filename)
  _sp_dirname=$(dirname "$1")
  _sp_basename=$(basename "$1")
    # determine suffix, if any
  _sp_suffix=$([[ $_sp_basename = *.* ]] && printf %s ".${_sp_basename##*.}" || printf '')
    # determine basename root (filemane w/o suffix)
  if [[ "$_sp_basename" == "$_sp_suffix" ]]; then # does filename start with '.'?
      _sp_basename_root=$_sp_basename
      _sp_suffix=''
  else # strip suffix from filename
    _sp_basename_root=${_sp_basename%$_sp_suffix}
  fi
  # assign to output vars.
  [[ -n $2 ]] && printf -v "$2" "$_sp_dirname"
  [[ -n $3 ]] && printf -v "$3" "$_sp_basename"
  [[ -n $4 ]] && printf -v "$4" "$_sp_basename_root"
  [[ -n $5 ]] && printf -v "$5" "$_sp_suffix"
  return 0
}

test_paths=(
  '/etc/bash.bashrc'
  '/usr/bin/grep'
  '/Users/jdoe/.bash_profile'
  '/Library/Application Support/'
  'readme.new.txt'
)

for p in "${test_paths[@]}"; do
  echo ----- "$p"
  parentpath= fname= fnameroot= suffix=
  splitPath "$p" parentpath fname fnameroot suffix
  for n in parentpath fname fnameroot suffix; do
    echo "$n=${!n}"
  done
done

Тестовый код, который выполняет функцию:

test_paths=(
  '/etc/bash.bashrc'
  '/usr/bin/grep'
  '/Users/jdoe/.bash_profile'
  '/Library/Application Support/'
  'readme.new.txt'
)

for p in "${test_paths[@]}"; do
  echo ----- "$p"
  parentpath= fname= fnameroot= suffix=
  splitPath "$p" parentpath fname fnameroot suffix
  for n in parentpath fname fnameroot suffix; do
    echo "$n=${!n}"
  done
done

Ожидаемый результат - обратите внимание на крайние случаи:

  • имя файла без суффикса
  • имя файла, начинающееся с . ( не считается началом суффикса)
  • входной путь, заканчивающийся / (завершающий / игнорируется)
  • входной путь, который является только именем файла (. возвращается как родительский путь)
  • имя файла с токеном с префиксом . (суффикс считается только последний):
----- /etc/bash.bashrc
parentpath=/etc
fname=bash.bashrc
fnameroot=bash
suffix=.bashrc
----- /usr/bin/grep
parentpath=/usr/bin
fname=grep
fnameroot=grep
suffix=
----- /Users/jdoe/.bash_profile
parentpath=/Users/jdoe
fname=.bash_profile
fnameroot=.bash_profile
suffix=
----- /Library/Application Support/
parentpath=/Library
fname=Application Support
fnameroot=Application Support
suffix=
----- readme.new.txt
parentpath=.
fname=readme.new.txt
fnameroot=readme.new
suffix=.txt
12 голосов
/ 29 сентября 2011

Я думаю, что если вам просто нужно имя файла, вы можете попробовать это:

FULLPATH=/usr/share/X11/xorg.conf.d/50-synaptics.conf

# Remove all the prefix until the "/" character
FILENAME=${FULLPATH##*/}

# Remove all the prefix until the "." character
FILEEXTENSION=${FILENAME##*.}

# Remove a suffix, in our case, the filename. This will return the name of the directory that contains this file.
BASEDIRECTORY=${FULLPATH%$FILENAME}

echo "path = $FULLPATH"
echo "file name = $FILENAME"
echo "file extension = $FILEEXTENSION"
echo "base directory = $BASEDIRECTORY"

И это все = D.

10 голосов
/ 10 сентября 2012

Вы можете принудительно вырезать для отображения всех полей и последующих, добавляя - к номеру поля.

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

Таким образом, если FILE равен eth0.pcap.gz, расширение будет pcap.gz

Используя ту же логику, вы также можете получить имя файла, используя '-' с вырезанным следующим образом:

NAME=`basename "$FILE" | cut -d'.' -f-1`

Это работает даже для имен файлов, которые не имеют никакого расширения.

7 голосов
/ 09 декабря 2011

Хорошо, поэтому, если я правильно понимаю, проблема здесь в том, как получить имя и полное расширение файла, который имеет несколько расширений, например, stuff.tar.gz.

Это работает для меня:

fullfile="stuff.tar.gz"
fileExt=${fullfile#*.}
fileName=${fullfile%*.$fileExt}

Это даст вам stuff в качестве имени файла и .tar.gz в качестве расширения. Он работает для любого количества расширений, включая 0. Надеюсь, это поможет всем, у кого возникла такая же проблема =)

7 голосов
/ 07 июля 2013

Волшебное распознавание файлов

В дополнение к множеству хороших ответов на этот вопрос переполнения стека, я хотел бы добавить:

В Linux и других unixen есть команда magic с именем file, которая определяет тип файла, анализируя некоторые первые байты файла. Это очень старый инструмент, изначально используемый для серверов печати (если он не создан для ... Я не уверен в этом).

file myfile.txt
myfile.txt: UTF-8 Unicode text

file -b --mime-type myfile.txt
text/plain

Стандартные расширения можно найти в /etc/mime.types (на моем Debian GNU / Linux рабочем столе. См. man file и man mime.types. Возможно, вам нужно установить утилиту file и mime-support пакеты):

grep $( file -b --mime-type myfile.txt ) </etc/mime.types
text/plain      asc txt text pot brf srt

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

file2ext() {
    local _mimetype=$(file -Lb --mime-type "$1") _line _basemimetype
    case ${_mimetype##*[/.-]} in
        gzip | bzip2 | xz | z )
            _mimetype=${_mimetype##*[/.-]}
            _mimetype=${_mimetype//ip}
            _basemimetype=$(file -zLb --mime-type "$1")
            ;;
        stream )
            _mimetype=($(file -Lb "$1"))
            [ "${_mimetype[1]}" = "compressed" ] &&
                _basemimetype=$(file -b --mime-type - < <(
                        ${_mimetype,,} -d <"$1")) ||
                _basemimetype=${_mimetype,,}
            _mimetype=${_mimetype,,}
            ;;
        executable )  _mimetype='' _basemimetype='' ;;
        dosexec )     _mimetype='' _basemimetype='exe' ;;
        shellscript ) _mimetype='' _basemimetype='sh' ;;
        * )
            _basemimetype=$_mimetype
            _mimetype=''
            ;;
    esac
    while read -a _line ;do
        if [ "$_line" == "$_basemimetype" ] ;then
            [ "$_line[1]" ] &&
                _basemimetype=${_line[1]} ||
                _basemimetype=${_basemimetype##*[/.-]}
            break
        fi
        done </etc/mime.types
    case ${_basemimetype##*[/.-]} in
        executable ) _basemimetype='' ;;
        shellscript ) _basemimetype='sh' ;;
        dosexec ) _basemimetype='exe' ;;
        * ) ;;
    esac
    [ "$_mimetype" ] && [ "$_basemimetype" != "$_mimetype" ] &&
      printf ${2+-v} $2 "%s.%s" ${_basemimetype##*[/.-]} ${_mimetype##*[/.-]} ||
      printf ${2+-v} $2 "%s" ${_basemimetype##*[/.-]}
}

Эта функция может установить переменную Bash, которую можно использовать позже:

(это вдохновлено @Petesh правильным ответом):

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

echo "$fullfile -> $filename . $extension"
6 голосов
/ 22 марта 2014

Я использую следующий скрипт

$ echo "foo.tar.gz"|rev|cut -d"." -f3-|rev
foo
5 голосов
/ 10 апреля 2011
$ F = "text file.test.txt"  
$ echo ${F/*./}  
txt  

Это обслуживает несколько точек и пробелов в имени файла, однако, если расширение отсутствует, оно возвращает само имя файла. Легко проверить, хотя; просто проверьте, совпадают ли имя файла и расширение.

Естественно, этот метод не работает для файлов .tar.gz. Однако это может быть обработано в два этапа. Если расширение - gz, проверьте еще раз, есть ли расширение tar.

4 голосов
/ 31 марта 2014

Как извлечь имя файла и расширение в fish :

function split-filename-extension --description "Prints the filename and extension"
  for file in $argv
    if test -f $file
      set --local extension (echo $file | awk -F. '{print $NF}')
      set --local filename (basename $file .$extension)
      echo "$filename $extension"
    else
      echo "$file is not a valid file"
    end
  end
end

Предостережения: Разбивает последнюю точку, что хорошо работает для имен файлов с точками в них, но не очень хорошо для расширений с точками в них. Смотрите пример ниже.

Использование:

$ split-filename-extension foo-0.4.2.zip bar.tar.gz
foo-0.4.2 zip  # Looks good!
bar.tar gz  # Careful, you probably want .tar.gz as the extension.

Возможно, есть лучшие способы сделать это. Не стесняйтесь редактировать мой ответ, чтобы улучшить его.


Если существует ограниченный набор расширений, с которыми вы будете иметь дело, и вы знаете их все, попробуйте следующее:

switch $file
  case *.tar
    echo (basename $file .tar) tar
  case *.tar.bz2
    echo (basename $file .tar.bz2) tar.bz2
  case *.tar.gz
    echo (basename $file .tar.gz) tar.gz
  # and so on
end

Это , а не - это предостережение в качестве первого примера, но вы должны обрабатывать каждый случай, чтобы он мог быть более утомительным в зависимости от того, сколько расширений вы можете ожидать.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...