Вопрос: Извлечь имя файла и расширение в Bash


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

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

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

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

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

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

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

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


1591


источник


Ответы:


Во-первых, получите имя файла без пути:

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

Кроме того, вы можете сосредоточиться на последнем «/» пути вместо «.». который должен работать, даже если у вас есть непредсказуемые расширения файлов:

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

2765



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

Подробнее см. расширение параметра оболочки в руководстве Bash.


494



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

basename filename .extension

например:

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

и мы получаем

filename

271



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



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

#!/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 = "/"
    base = ""
    ext = ""
/ Главная / мне /:
    dir = "/ home / me /"
    base = ""
    ext = ""
/ Главная / мне / файл:
    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"
/ Главная / мне / ..:
    dir = "/ home / me /"
    base = ".."
    ext = ""
.:
    dir = ""
    base = "."
    ext = "" 

65



Вы можете использовать basename,

Пример:

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

Вам необходимо предоставить базовое имя с расширением, которое должно быть удалено, однако, если вы всегда выполняете tarс -zто вы знаете, что расширение будет .tar.gz,

Это должно делать то, что вы хотите:

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

40



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

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

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

Выходы:

filename: thisfile
extension: txt

24



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

works fine, so you can just use:

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

The commands, by the way, work as follows.

The command for NAME substitutes a "." character followed by any number of non-"." characters up to the end of the line, with nothing (i.e., it removes everything from the final "." to the end of the line, inclusive). This is basically a non-greedy substitution using regex trickery.

The command for EXTENSION substitutes a any number of characters followed by a "." character at the start of the line, with nothing (i.e., it removes everything from the start of the line to the final dot, inclusive). This is a greedy substitution which is the default action.


23



You could use the cut command to remove the last two extensions (the ".tar.gz" part):

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

As noted by Clayton Hughes in a comment, this will not work for the actual example in the question. So as an alternative I propose using sed with extended regular expressions, like this:

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

It works by removing the last two (alpha-numeric) extensions unconditionally.

[Updated again after comment from Anders Lindahl]


22