Извлечь имя файла и расширение в Bash
Я хочу получить имя файла (без расширения) и расширение отдельно.
Лучшее решение, которое я нашел, это:
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 только для этого, если это возможно.
Есть идеи получше?
38 ответов
Сначала получите имя файла без пути:
filename=$(basename -- "$fullfile")
extension="${filename##*.}"
filename="${filename%.*}"
Кроме того, вы можете сосредоточиться на последнем "/" пути вместо "." который должен работать, даже если у вас есть непредсказуемые расширения файлов:
filename="${fullfile##*/}"
Вы можете проверить документацию:
- В Интернете в разделе " 3.5.3 Расширение параметров оболочки"
- На странице руководства bash в разделе "Расширение параметров"
~% FILE="example.tar.gz"
~% echo "${FILE%%.*}"
example
~% echo "${FILE%.*}"
example.tar
~% echo "${FILE#*.}"
tar.gz
~% echo "${FILE##*.}"
gz
Для получения дополнительной информации см. Расширение параметров оболочки в руководстве по Bash.
Обычно вы уже знаете расширение, поэтому вы можете использовать:
basename filename .extension
например:
basename /path/to/dir/filename.txt .txt
и мы получаем
filename
Вы можете использовать магию переменных 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
Это не работает, если файл не имеет расширения или не имеет имени файла. Вот что я использую; он использует только встроенные функции и обрабатывает больше (но не все) патологических имен файлов.
#!/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 = "/" база = "" ext = "" / Главная / мне /: dir = "/ home / me /" база = "" ext = "" / Главная / мне / файл: dir = "/ home / me /" база = "файл" ext = "" /home/me/file.tar: dir = "/ home / me /" база = "файл" 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 /" база = ".." ext = "".: dir = "" база = "." ext = ""
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
заменяет любое количество символов, за которыми следует "."
символ в начале строки, с ничем (т.е. он удаляет все от начала строки до конечной точки включительно). Это жадная замена, которая является действием по умолчанию.
Ты можешь использовать basename
,
Пример:
$ basename foo-bar.tar.gz .tar.gz
foo-bar
Вам нужно предоставить базовое имя с расширением, которое должно быть удалено, однако, если вы всегда выполняете tar
с -z
тогда вы знаете, что расширение будет .tar.gz
,
Это должно делать то, что вы хотите:
tar -zxvf $1
cd $(basename $1 .tar.gz)
Меллен пишет в комментарии к сообщению в блоге:
Используя Bash, есть также ${file%.*}
чтобы получить имя файла без расширения и ${file##*.}
чтобы получить расширение в одиночку. То есть,
file="thisfile.txt"
echo "filename: ${file%.*}"
echo "extension: ${file##*.}"
Выходы:
filename: thisfile
extension: 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 '^[/]\|^~/'
Во всех случаях использования в качестве входных данных используется исходный полный путь, не зависящий от промежуточных результатов.
Не нужно беспокоиться с awk
или же sed
или даже perl
для этой простой задачи. Есть чистый Bash, os.path.splitext()
-совместимое решение, которое использует только расширения параметров.
Реализация ссылок
Документация os.path.splitext(path)
:
Разбить путь пути в пару
(root, ext)
такой, чтоroot + ext == path
и ext пусто или начинается с точки и содержит не более одного периода. Ведущие периоды на базовом имени игнорируются;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' |
|---------------|-----------|-------|
Результаты теста
Все тесты пройдены.
[Пересмотрен с однострочной на общую функцию 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'
Обратите внимание, что аргументы после входного пути выбираются свободно, имена позиционных переменных.
Чтобы пропустить переменные, не представляющие интереса, которые предшествуют тем, которые есть _
(использовать одноразовую переменную $_
) или же ''
; например, чтобы извлечь только имя файла и расширение, используйте 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
Вы могли бы использовать 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
Он работает, удаляя последние два (буквенно-цифровые) расширения безоговорочно.
[Обновлено снова после комментария от Андерса Линдала]
Наименьшее и простое решение (в одну строку) это:
$ file=/blaabla/bla/blah/foo.txt
echo $(basename ${file%.*}) # foo
Я думаю, что если вам просто нужно имя файла, вы можете попробовать это:
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.
Вы можете принудительно вырезать, чтобы отобразить все поля и последующие добавления -
на номер поля.
NAME=`basename "$FILE"`
EXTENSION=`echo "$NAME" | cut -d'.' -f2-`
Так что, если ФАЙЛ eth0.pcap.gz
РАСШИРЕНИЕ будет pcap.gz
Используя ту же логику, вы также можете получить имя файла, используя '-' с cut следующим образом:
NAME=`basename "$FILE" | cut -d'.' -f-1`
Это работает даже для имен файлов, которые не имеют никакого расширения.
$ F = "text file.test.txt"
$ echo ${F/*./}
txt
Это обслуживает несколько точек и пробелов в имени файла, однако, если нет расширения, оно возвращает само имя файла. Легко проверить, хотя; просто проверьте, что имя файла и расширение совпадают.
Естественно, этот метод не работает для файлов.tar.gz. Однако это может быть обработано в два этапа. Если расширение - gz, проверьте еще раз, есть ли расширение tar.
Это единственный, который у меня сработал:
path='folder/other_folder/file.js'
base=${path##*/}
echo ${base%.*}
>> file
Это также можно использовать при интерполяции строк, но, к сожалению, вам нужно установить base
заранее.
Волшебное распознавание файлов
В дополнение к множеству хороших ответов на этот вопрос переполнения стека я хотел бы добавить:
Под Linux и другими unixen есть волшебная команда с именем 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
Вы можете создать функцию 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"
Итак, если я правильно понимаю, проблема здесь в том, как получить имя и полное расширение файла, который имеет несколько расширений, например, stuff.tar.gz
,
Это работает для меня:
fullfile="stuff.tar.gz"
fileExt=${fullfile#*.}
fileName=${fullfile%*.$fileExt}
Это даст вам stuff
как имя файла и .tar.gz
как расширение. Он работает для любого количества расширений, включая 0. Надеюсь, это поможет всем, у кого возникла такая же проблема =)
Просто использовать ${parameter%word}
В твоем случае:
${FILE%.*}
Если вы хотите проверить это, все последующие работы и просто удалите расширение:
FILE=abc.xyz; echo ${FILE%.*};
FILE=123.abc.xyz; echo ${FILE%.*};
FILE=abc; echo ${FILE%.*};
В предыдущем ответе не использовалось регулярное выражение bash
. Вот чистое решение ERE для bash, которое разбивает путь на:
- Путь к каталогу с его окончанием, если оно присутствует
. Регулярное выражение, которое отбрасывает замыкание, настолько длиннее, что я его не публиковал. - Имя файла , исключая (последнее) точечное расширение
- (Последнее) точечное расширение с ведущим
Код предназначен для обработки всех возможных случаев, вы можете попробовать его.
#!/bin/bash
for path; do
####### the relevant part ######
[[ $path =~ ^(\.{1,2}|.*/\.{0,2})$|^(.*/)([^/]+)(\.[^/]*)$|^(.*/)(.+)$|^(.+)(\..*)$|^(.+)$ ]]
dirpath="${BASH_REMATCH[1]}${BASH_REMATCH[2]}${BASH_REMATCH[5]}"
filename="${BASH_REMATCH[3]}${BASH_REMATCH[6]}${BASH_REMATCH[7]}${BASH_REMATCH[9]}"
filext="${BASH_REMATCH[4]}${BASH_REMATCH[8]}"
# dirpath should be non-null
[[ $dirpath ]] || dirpath='.'
################################
printf '%s=%q\n' \
path "$path" \
dirpath "$dirpath" \
filename "$filename" \
filext "$filext"
done
Как это работает?
По сути, это гарантирует, что только одно подвыражение (разделенное
Вот результаты расширенного, но не исчерпывающего набора примеров:
+--------------------------------------------------------+
| input dirpath filename filext |
+--------------------------------------------------------+
'' . '' ''
. . '' ''
.. .. '' ''
... . .. .
.file . .file ''
.file. . .file .
.file.. . .file. .
.file.Z . .file .Z
.file.sh.Z . .file.sh .Z
file . file ''
file. . file .
file.. . file. .
file.Z . file .Z
file.sh.Z . file.sh .Z
dir/ dir/ '' ''
dir/. dir/. '' ''
dir/... dir/ .. .
dir/.file dir/ .file ''
dir/.file. dir/ .file .
dir/.file.. dir/ .file. .
dir/.file.Z dir/ .file .Z
dir/.file.x.Z dir/ .file.x .Z
dir/file dir/ file ''
dir/file. dir/ file .
dir/file.. dir/ file. .
dir/file.Z dir/ file .Z
dir/file.x.Z dir/ file.x .Z
dir./. dir./. '' ''
dir./... dir./ .. .
dir./.file dir./ .file ''
dir./.file. dir./ .file .
dir./.file.. dir./ .file. .
dir./.file.Z dir./ .file .Z
dir./.file.sh.Z dir./ .file.sh .Z
dir./file dir./ file ''
dir./file. dir./ file .
dir./file.. dir./ file. .
dir./file.Z dir./ file .Z
dir./file.x.Z dir./ file.x .Z
dir// dir// '' ''
dir//. dir//. '' ''
dir//... dir// .. .
dir//.file dir// .file ''
dir//.file. dir// .file .
dir//.file.. dir// .file. .
dir//.file.Z dir// .file .Z
dir//.file.x.Z dir// .file.x .Z
dir//file dir// file ''
dir//file. dir// file .
dir//file.. dir// file. .
dir//file.Z dir// file .Z
dir//file.x.Z dir// file.x .Z
dir.//. dir.//. '' ''
dir.//... dir.// .. .
dir.//.file dir.// .file ''
dir.//.file. dir.// .file .
dir.//.file.. dir.// .file. .
dir.//.file.Z dir.// .file .Z
dir.//.file.x.Z dir.// .file.x .Z
dir.//file dir.// file ''
dir.//file. dir.// file .
dir.//file.. dir.// file. .
dir.//file.Z dir.// file .Z
dir.//file.x.Z dir.// file.x .Z
/ / '' ''
/. /. '' ''
/.. /.. '' ''
/... / .. .
/.file / .file ''
/.file. / .file .
/.file.. / .file. .
/.file.Z / .file .Z
/.file.sh.Z / .file.sh .Z
/file / file ''
/file. / file .
/file.. / file. .
/file.Z / file .Z
/file.sh.Z / file.sh .Z
/dir/ /dir/ '' ''
/dir/. /dir/. '' ''
/dir/... /dir/ .. .
/dir/.file /dir/ .file ''
/dir/.file. /dir/ .file .
/dir/.file.. /dir/ .file. .
/dir/.file.Z /dir/ .file .Z
/dir/.file.x.Z /dir/ .file.x .Z
/dir/file /dir/ file ''
/dir/file. /dir/ file .
/dir/file.. /dir/ file. .
/dir/file.Z /dir/ file .Z
/dir/file.x.Z /dir/ file.x .Z
/dir./. /dir./. '' ''
/dir./... /dir./ .. .
/dir./.file /dir./ .file ''
/dir./.file. /dir./ .file .
/dir./.file.. /dir./ .file. .
/dir./.file.Z /dir./ .file .Z
/dir./.file.sh.Z /dir./ .file.sh .Z
/dir./file /dir./ file ''
/dir./file. /dir./ file .
/dir./file.. /dir./ file. .
/dir./file.Z /dir./ file .Z
/dir./file.x.Z /dir./ file.x .Z
/dir// /dir// '' ''
/dir//. /dir//. '' ''
/dir//... /dir// .. .
/dir//.file /dir// .file ''
/dir//.file. /dir// .file .
/dir//.file.. /dir// .file. .
/dir//.file.Z /dir// .file .Z
/dir//.file.x.Z /dir// .file.x .Z
/dir//file /dir// file ''
/dir//file. /dir// file .
/dir//file.. /dir// file. .
/dir//file.Z /dir// file .Z
/dir//file.x.Z /dir// file.x .Z
/dir.//. /dir.//. '' ''
/dir.//... /dir.// .. .
/dir.//.file /dir.// .file ''
/dir.//.file. /dir.// .file .
/dir.//.file.. /dir.// .file. .
/dir.//.file.Z /dir.// .file .Z
/dir.//.file.x.Z /dir.// .file.x .Z
/dir.//file /dir.// file ''
/dir.//file. /dir.// file .
/dir.//file.. /dir.// file. .
/dir.//file.Z /dir.// file .Z
/dir.//file.x.Z /dir.// file.x .Z
// // '' ''
//. //. '' ''
//.. //.. '' ''
//... // .. .
//.file // .file ''
//.file. // .file .
//.file.. // .file. .
//.file.Z // .file .Z
//.file.sh.Z // .file.sh .Z
//file // file ''
//file. // file .
//file.. // file. .
//file.Z // file .Z
//file.sh.Z // file.sh .Z
//dir/ //dir/ '' ''
//dir/. //dir/. '' ''
//dir/... //dir/ .. .
//dir/.file //dir/ .file ''
//dir/.file. //dir/ .file .
//dir/.file.. //dir/ .file. .
//dir/.file.Z //dir/ .file .Z
//dir/.file.x.Z //dir/ .file.x .Z
//dir/file //dir/ file ''
//dir/file. //dir/ file .
//dir/file.. //dir/ file. .
//dir/file.Z //dir/ file .Z
//dir/file.x.Z //dir/ file.x .Z
//dir./. //dir./. '' ''
//dir./... //dir./ .. .
//dir./.file //dir./ .file ''
//dir./.file. //dir./ .file .
//dir./.file.. //dir./ .file. .
//dir./.file.Z //dir./ .file .Z
//dir./.file.sh.Z //dir./ .file.sh .Z
//dir./file //dir./ file ''
//dir./file. //dir./ file .
//dir./file.. //dir./ file. .
//dir./file.Z //dir./ file .Z
//dir./file.x.Z //dir./ file.x .Z
//dir// //dir// '' ''
//dir//. //dir//. '' ''
//dir//... //dir// .. .
//dir//.file //dir// .file ''
//dir//.file. //dir// .file .
//dir//.file.. //dir// .file. .
//dir//.file.Z //dir// .file .Z
//dir//.file.x.Z //dir// .file.x .Z
//dir//file //dir// file ''
//dir//file. //dir// file .
//dir//file.. //dir// file. .
//dir//file.Z //dir// file .Z
//dir//file.x.Z //dir// file.x .Z
//dir.//. //dir.//. '' ''
//dir.//... //dir.// .. .
//dir.//.file //dir.// .file ''
//dir.//.file. //dir.// .file .
//dir.//.file.. //dir.// .file. .
//dir.//.file.Z //dir.// .file .Z
//dir.//.file.x.Z //dir.// .file.x .Z
//dir.//file //dir.// file ''
//dir.//file. //dir.// file .
//dir.//file.. //dir.// file. .
//dir.//file.Z //dir.// file .Z
//dir.//file.x.Z //dir.// file.x .Z
Как видите, поведение отличается от
Я рассчитал время с 10000 путями по 256 символов, и это заняло около 1 секунды, в то время как эквивалентное решение оболочки POSIX работает в 2 раза медленнее, а решения, основанные на диких разветвлениях (внешние вызовы внутри
примечание: нет необходимости тестировать пути, содержащие
Кроме того, вот чистое решение оболочки POSIX, которое дает тот же результат:
#!/bin/sh
for path; do
####### the relevant part ######
basename=${path##*/}
case $basename in
. | ..)
dirpath="$path"
filename=''
filext=''
basename=''
;;
*)
dirpath=${path%"$basename"}
filename=${basename#.}
filename="${basename%"$filename"}${filename%.*}"
filext=${basename#"$filename"}
esac
# dirpath should be non-null
[ -z "$dirpath" ] && dirpath='.'
################################
printf '%s=%s\n' \
path "$filepath" \
dirpath "$dirpath" \
filename "$filename" \
filext "$filext"
done
постскриптум: Есть несколько моментов, по которым некоторые люди могут не согласиться с результатами, полученными с помощью приведенных выше кодов:
Особый случай точечных файлов : причина в том, что точечные файлы — это концепция UNIX.
Особый случай
а также : ИМХО кажется очевидным рассматривать их как каталоги, но большинство библиотек этого не делают и заставляют пользователя постобрабатывать результат. Нет поддержки двойных расширений: это потому, что вам потребуется целая база данных для хранения всех действительных двойных расширений, и, прежде всего, потому что расширение файла ничего не значит в UNIX; например, вы можете вызвать tar-архив
и это совершенно нормально, вы сможете без каких-либо проблем.
Я использую следующий скрипт
$ echo "foo.tar.gz"|rev|cut -d"." -f3-|rev
foo
Как извлечь имя файла и расширение в рыбе:
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
В этом примере нет предостережения в качестве первого примера, но вы должны обрабатывать каждый случай, чтобы он мог быть более утомительным в зависимости от того, сколько расширений вы можете ожидать.
Вот код с 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
Построение из ответа Petesh, если требуется только имя файла, путь и расширение могут быть разделены в одну строку,
filename=$(basename ${fullname%.*})
Основанный в основном на превосходном @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>
Простой ответ:
Чтобы раскрыть ответ попеременным POSIX, обратите внимание, что вы можете создавать более интересные шаблоны. Таким образом, для случая, описанного здесь, вы можете просто сделать это:
tar -zxvf $1
cd ${1%.tar.*}
Это прервет последнее появление.tar.<что-то>
В более общем случае, если вы хотите удалить последнее вхождение.<что-то><что-то еще> тогда
${1.*.*}
должно работать нормально.
Ссылка на ответ выше кажется мертвой. Вот отличное объяснение множества манипуляций со строками, которые вы можете выполнять непосредственно в Bash из TLDP.
Если вы также хотите разрешить пустые расширения, это самое короткое, что я могу придумать:
echo 'hello.txt' | sed -r 's/.+\.(.+)|.*/\1/' # EXTENSION
echo 'hello.txt' | sed -r 's/(.+)\..+|(.*)/\1\2/' # FILENAME
1-я строка объяснила: он соответствует PATH.EXT или НИЧЕГО и заменяет его на EXT. Если НИЧЕГО было найдено, группа ext не перехватывается.
ИМХО, лучшее решение уже было дано (с использованием расширения параметров оболочки) и является лучшим на данный момент.
Я, однако, добавляю эту, которая просто использует команды dumbs, которая неэффективна и которую никто никогда не должен использовать серьезно:
FILENAME=$(echo $FILE | cut -d . -f 1-$(printf $FILE | tr . '\n' | wc -l))
EXTENSION=$(echo $FILE | tr . '\n' | tail -1)
Добавлено просто для удовольствия:-)
Вот алгоритм, который я использовал для нахождения имени и расширения файла, когда писал сценарий Bash, чтобы сделать имена уникальными, когда имена конфликтуют по отношению к регистру.
#! /bin/bash
#
# Finds
# -- name and extension pairs
# -- null extension when there isn't an extension.
# -- Finds name of a hidden file without an extension
#
declare -a fileNames=(
'.Montreal'
'.Rome.txt'
'Loundon.txt'
'Paris'
'San Diego.txt'
'San Francisco'
)
echo "Script ${0} finding name and extension pairs."
echo
for theFileName in "${fileNames[@]}"
do
echo "theFileName=${theFileName}"
# Get the proposed name by chopping off the extension
name="${theFileName%.*}"
# get extension. Set to null when there isn't an extension
# Thanks to mklement0 in a comment above.
extension=$([[ "$theFileName" == *.* ]] && echo ".${theFileName##*.}" || echo '')
# a hidden file without extenson?
if [ "${theFileName}" = "${extension}" ] ; then
# hidden file without extension. Fixup.
name=${theFileName}
extension=""
fi
echo " name=${name}"
echo " extension=${extension}"
done
Тестовый прогон.
$ config/Name\&Extension.bash
Script config/Name&Extension.bash finding name and extension pairs.
theFileName=.Montreal
name=.Montreal
extension=
theFileName=.Rome.txt
name=.Rome
extension=.txt
theFileName=Loundon.txt
name=Loundon
extension=.txt
theFileName=Paris
name=Paris
extension=
theFileName=San Diego.txt
name=San Diego
extension=.txt
theFileName=San Francisco
name=San Francisco
extension=
$
К вашему сведению: полную программу транслитерации и другие тестовые примеры можно найти здесь: https://www.dropbox.com/s/4c6m0f2e28a1vxf/avoid-clashes-code.zip?dl=0