bash - убрать - linux выделить имя файла




Извлечь имя файла и расширение в Bash (20)

Магическое распознавание файлов

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

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

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 и man mime.types . Возможно, вам нужно установить file утилиты и пакеты mime-support ):

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

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

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"

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

Лучшее решение, которое я нашел до сих пор:

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

Это неправильно, потому что это не работает, если имя файла содержит несколько . персонажи. Если, скажем, у меня есть abjs , он рассмотрит a и b.js , а не ab и js .

Это можно легко сделать в Python с помощью

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

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

Любые лучшие идеи?


[Пересмотрено от однострочного к общей функции bash, поведение теперь совместимо с dirname и basename утилитами; обоснование добавлено.]

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

  • Для имен файлов без расширения (называемых суффиксами в оставшейся части этого ответа) 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'

Обратите внимание, что аргументы после входного пути свободно выбираются, имена позиционных переменных.
Чтобы пропустить переменные, не представляющие интерес, которые появляются перед теми, которые есть, укажите _ (использовать переменную throw-away $_ ) или '' ; например, для извлечения корневого имени и расширения только имени, используйте 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

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

  • имя файла без суффикса
  • имя файла, начинающееся с . ( не считается началом суффикса)
  • входной путь, заканчивающийся на / (трейлинг / игнорируется)
  • входной путь, который является только именем файла (возвращается в качестве родительского пути)
  • имя файла, которое имеет больше, чем . -prefixed token (только последний считается суффиксом):
----- /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

Вот код с AWK . Это можно сделать проще. Но я не очень хорош в AWK.

filename$ ls
abc.a.txt  a.b.c.txt  pp-kk.txt
filename$ find . -type f | awk -F/ '{print $2}' | rev | awk -F"." '{$1="";print}' | rev | awk 'gsub(" ",".") ,sub(".$", "")'
abc.a
a.b.c
pp-kk
filename$ find . -type f | awk -F/ '{print $2}' | awk -F"." '{print $NF}'
txt
txt
txt

Вот несколько альтернативных предложений (в основном в 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 '^[/]\|^~/'

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


Вы можете использовать команду cut чтобы удалить последние два расширения (часть ".tar.gz" ):

$ echo "foo.tar.gz" | cut -d'.' --complement -f2-
foo

Как отметил Клейтон Хьюз в комментарии, это не будет работать для фактического примера в вопросе. Поэтому в качестве альтернативы я предлагаю использовать sed с расширенными регулярными выражениями, например:

$ echo "mpc-1.0.1.tar.gz" | sed -r 's/\.[[:alnum:]]+\.[[:alnum:]]+$//'
mpc-1.0.1

Он работает, удаляя последние два (альфа-числовые) расширения безоговорочно.

[Обновлено после комментария Андерса Линдаля]


Вы можете использовать магию переменных 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%%.*}

)

Этот site объясняет больше.

${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

Как извлечь имя файла и расширение в 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

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


Меллен пишет в комментарии к сообщению в блоге:

Используя Bash, есть ${file%.*} Чтобы получить имя файла без расширения и ${file##*.} Чтобы получить расширение самостоятельно. То есть,

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

Выходы:

filename: thisfile
extension: txt

Не нужно беспокоиться об awk или sed или даже perl для этой простой задачи. Существует решение pure-Bash, os.path.splitext() которое использует только разложения параметров.

Реализация ссылок

Документация os.path.splitext(path) :

Разделите путь пути в пару (root, ext) , так что root + ext == path и ext пуст или начинается с периода и содержит не более одного периода. Ведущие периоды в basename игнорируются; splitext('.cshrc') возвращает ('.cshrc', '') .

Код Python:

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

Реализация Bash

Почитание ведущих периодов

root="${path%.*}"
ext="${path#"$root"}"

Игнорирование ведущих периодов

root="${path#.}";root="${path%"$root"}${root%.*}"
ext="${path#"$root"}"

тесты

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

|---------------|-----------|-------|
|path           |root       |ext    |
|---------------|-----------|-------|
|' .txt'        |' '        |'.txt' |
|' .txt.txt'    |' .txt'    |'.txt' |
|' txt'         |' txt'     |''     |
|'*.txt.txt'    |'*.txt'    |'.txt' |
|'.cshrc'       |'.cshrc'   |''     |
|'.txt'         |'.txt'     |''     |
|'?.txt.txt'    |'?.txt'    |'.txt' |
|'\n.txt.txt'   |'\n.txt'   |'.txt' |
|'\t.txt.txt'   |'\t.txt'   |'.txt' |
|'a b.txt.txt'  |'a b.txt'  |'.txt' |
|'a*b.txt.txt'  |'a*b.txt'  |'.txt' |
|'a?b.txt.txt'  |'a?b.txt'  |'.txt' |
|'a\nb.txt.txt' |'a\nb.txt' |'.txt' |
|'a\tb.txt.txt' |'a\tb.txt' |'.txt' |
|'txt'          |'txt'      |''     |
|'txt.pdf'      |'txt'      |'.pdf' |
|'txt.tar.gz'   |'txt.tar'  |'.gz'  |
|'txt.txt'      |'txt'      |'.txt' |
|---------------|-----------|-------|

Результаты теста

Все тесты прошли.


Обычно вы уже знаете расширение, поэтому вы можете использовать:

basename filename .extension

например:

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

и мы получаем

filename

Ты можешь использовать

sed 's/^/./' | rev | cut -d. -f2- | rev | cut -c2-

для получения имени файла и

sed 's/^/./' | rev | cut -d. -f1  | rev

получить расширение.

Прецедент:

echo "filename.gz"     | sed 's/^/./' | rev | cut -d. -f2- | rev | cut -c2-
echo "filename.gz"     | sed 's/^/./' | rev | cut -d. -f1  | rev
echo "filename"        | sed 's/^/./' | rev | cut -d. -f2- | rev | cut -c2-
echo "filename"        | sed 's/^/./' | rev | cut -d. -f1  | rev
echo "filename.tar.gz" | sed 's/^/./' | rev | cut -d. -f2- | rev | cut -c2-
echo "filename.tar.gz" | sed 's/^/./' | rev | cut -d. -f1  | rev

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

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

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

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


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

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.


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

$ echo "foo.tar.gz"|rev|cut -d"." -f3-|rev
foo

Из ответов выше, кратчайший oneliner, чтобы имитировать Python's

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

предполагая, что ваш файл действительно имеет расширение, является

EXT="${PATH##*.}"; FILE=$(basename "$PATH" .$EXT)

Просто используйте ${parameter%word}

В твоем случае:

${FILE%.*}

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

FILE=abc.xyz; echo ${FILE%.*};
FILE=123.abc.xyz; echo ${FILE%.*};
FILE=abc; echo ${FILE%.*};

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

echo 'hello.txt' | sed -r 's/.+\.(.+)|.*/\1/' # EXTENSION
echo 'hello.txt' | sed -r 's/(.+)\..+|(.*)/\1\2/' # FILENAME

1-я строка объяснила: он соответствует PATH.EXT или НИЧЕГО и заменяет его EXT. Если ANYTHING был сопоставлен, группа ext не будет захвачена.


Основанный в значительной степени от превосходного @ mklement0 и полного набора случайных, полезных базизмов, а также других ответов на этот / другие вопросы / «которые прокляты в Интернете» ... Я завернул все это в немного, немного более понятным, функция повторного использования для моего (или вашего), .bash_profileкоторый заботится о том, что (я считаю) должно быть более надежной версией dirname/ basename/ что у вас есть .

function path { SAVEIFS=$IFS; IFS=""   # stash IFS for safe-keeping, etc.
    [[ $# != 2 ]] && echo "usage: path <path> <dir|name|fullname|ext>" && return    # demand 2 arguments
    [[ $1 =~ ^(.*/)?(.+)?$ ]] && {     # regex parse the path
        dir=${BASH_REMATCH[1]}
        file=${BASH_REMATCH[2]}
        ext=$([[ $file = *.* ]] && printf %s ${file##*.} || printf '')
        # edge cases for extesionless files and files like ".nesh_profile.coffee"
        [[ $file == $ext ]] && fnr=$file && ext='' || fnr=${file:0:$((${#file}-${#ext}))}
        case "$2" in
             dir) echo      "${dir%/*}"; ;;
            name) echo      "${fnr%.*}"; ;;
        fullname) echo "${fnr%.*}.$ext"; ;;
             ext) echo           "$ext"; ;;
        esac
    }
    IFS=$SAVEIFS
}     

Примеры использования ...

SOMEPATH=/path/to.some/.random\ file.gzip
path $SOMEPATH dir        # /path/to.some
path $SOMEPATH name       # .random file
path $SOMEPATH ext        # gzip
path $SOMEPATH fullname   # .random file.gzip                     
path gobbledygook         # usage: -bash <path> <dir|name|fullname|ext>

$ F = "text file.test.txt"  
$ echo ${F/*./}  
txt  

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

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


pax> echo a.b.js | sed 's/\.[^.]*$//'
a.b
pax> echo a.b.js | sed 's/^.*\.//'
js

отлично работает, поэтому вы можете просто использовать:

pax> FILE=a.b.js
pax> NAME=$(echo "$FILE" | sed 's/\.[^.]*$//')
pax> EXTENSION=$(echo "$FILE" | sed 's/^.*\.//')
pax> echo $NAME
a.b
pax> echo $EXTENSION
js

Команды, кстати, работают следующим образом.

Команда NAME заменяет "." символ, за которым следует любое количество не "." символов до конца строки, ничего (т. е. удаляет все из окончательного "." до конца строки, включительно). Это, в основном, не-жадная замена с использованием регулярных выражений.

Команда EXTENSION заменяет любое количество символов, за которыми следует символ "." символ в начале строки, без ничего (т. е. удаляет все с начала строки до конечной точки включительно). Это жадная подстановка, которая является действием по умолчанию.





filenames