Извлечение имени файла и расширения в 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 только для этого, если это возможно.
Есть ли более хорошие идеи?
5 ответ(ов)
Обычно вы уже знаете расширение, поэтому вы можете использовать следующую команду:
basename filename .extension
Например:
basename /path/to/dir/filename.txt .txt
В результате получите:
filename
Проблема, с которой вы столкнулись, связана с обработкой файлов, у которых нет расширения или имени файла. Ваш скрипт действительно справляется с большим количеством "парадоксальных" имён файлов, но есть некоторые нюансы, которые следует учесть.
Вот исправленный вариант вашего скрипта, который более корректно обрабатывает такие случаи:
#!/bin/bash
for fullpath in "$@"
do
filename="${fullpath##*/}" # Убираем путь до файла
dir="${fullpath:0:${#fullpath} - ${#filename}}" # Получаем путь к директории
base="${filename%.[^.]*}" # Извлекаем базовое имя
ext="${filename:${#base} + 1}" # Получаем расширение
if [[ -z "$base" && -n "$ext" ]]; then # Если есть расширение, но нет базового имени
base=".$ext" # Перезаписываем base
ext="" # Убираем расширение
fi
# Обработка случая, когда filename пустое (например, для / или /dir/)
if [[ -z "$filename" ]]; then
base=""
ext=""
fi
echo -e "$fullpath:\n\tdir = \"$dir\"\n\tbase = \"$base\"\n\text = \"$ext\""
done
С помощью этого обновлённого кода скрипт корректно работает с файлами, у которых нет имени или расширения (например, /, /home/
) и обрабатывает различные ситуации. Вот примеры вывода для ваших тестов:
$ 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 = ""
/home/me/:
dir = "/home/me/"
base = ""
ext = ""
/home/me/file:
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"
/home/me/..:
dir = "/home/me/"
base = ".."
ext = ""
.:
dir = ""
base = "."
ext = ""
Этот скрипт теперь более устойчив к различным крайним случаям с именами файлов, что позволит корректно обрабатывать разнообразные ситуации.
Для извлечения имени файла и его расширения с помощью sed
, можно использовать следующие команды:
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
использует замену, которая ищет символ"."
, за которым следуют любые символы, кроме"."
, до конца строки. Она заменяет найденную строку на пустую строку, то есть удаляет всё, начиная с последней точки и до конца строки. Это своего рода «негрековый» (non-greedy) шаблон.Команда для получения
EXTENSION
заменяет любое количество символов, заканчивающееся на символ"."
, что находится в начале строки, на пустую строку. Это удаляет всё с начала строки до последней точки, включая её. Это «жадная» (greedy) замена, что является поведением по умолчанию для таких операций.
Таким образом, с помощью этих команд вы сможете легко извлекать имя файла и его расширение из полного имени файла.
В ответ на ваш вопрос, можно использовать следующие конструкции в Bash для работы с именами файлов:
Чтобы получить имя файла без расширения, вы можете использовать ${file%.*}
, а чтобы получить только расширение, используйте ${file##*.}
. Пример использования:
file="thisfile.txt"
echo "имя файла: ${file%.*}"
echo "расширение: ${file##*.}"
Вывод будет таким:
имя файла: thisfile
расширение: txt
Таким образом, эти конструкции позволяют легко извлекать имя файла и его расширение из полного имени файла.
Вот несколько альтернативных предложений (в основном на awk
), включая некоторые продвинутые примеры использования, такие как извлечение номеров версий для программных пакетов.
Обратите внимание, что при несколько изменённом вводе некоторые из них могут не сработать. Поэтому всем, кто использует эти команды, следует проверять их на ожидаемом входе и при необходимости адаптировать регулярные выражения.
f='/path/to/complex/file.1.0.1.tar.gz'
# Имя файла : 'file.1.0.x.tar.gz'
echo "$f" | awk -F'/' '{print $NF}'
# Расширение (последнее): 'gz'
echo "$f" | awk -F'[.]' 'NF>1 {print $NF}'
# Расширение (все) : '1.0.1.tar.gz'
echo "$f" | awk '{sub(/[^.]*[.]/, "", $0)} 1'
# Расширение (последние-2): 'tar.gz'
echo "$f" | awk -F'[.]' '{print $(NF-1)"."$NF}'
# Имя базового файла : 'file'
echo "$f" | awk '{gsub(/.*[/]|[.].*/, "", $0)} 1'
# Расширенное имя базового файла : 'file.1.0.1.tar'
echo "$f" | awk '{gsub(/.*[/]|[.]{1}[^.]+$/, "", $0)} 1'
# Путь : '/path/to/complex/'
echo "$f" | awk '{match($0, /.*[/]/, a); print a[0]}'
# или
echo "$f" | grep -Eo '.*[/]'
# Папка (содержащая файл) : 'complex'
echo "$f" | awk -F'/' '{$1=""; print $(NF-1)}'
# Версия : '1.0.1'
# Определяется как 'число.число' или 'число.число.число'
echo "$f" | grep -Eo '[0-9]+[.]+[0-9]+[.]?[0-9]?'
# Основная версия : '1'
echo "$f" | grep -Eo '[0-9]+[.]+[0-9]+[.]?[0-9]?' | cut -d. -f1
# Вспомогательная версия : '0'
echo "$f" | grep -Eo '[0-9]+[.]+[0-9]+[.]?[0-9]?' | cut -d. -f2
# Версия патча : '1'
echo "$f" | grep -Eo '[0-9]+[.]+[0-9]+[.]?[0-9]?' | cut -d. -f3
# Все компоненты : "путь к сложному файлу 1 0 1 tar gz"
echo "$f" | awk -F'[/.]' '{$1=""; print $0}'
# Является абсолютным : True (код выхода : 0)
# Вернёт true, если это абсолютный путь (начинается с '/' или '~/'
echo "$f" | grep -q '^[/]\|^~/'
Все примеры используют оригинальный полный путь как входные данные, не полагаясь на промежуточные результаты.
Как проверить, содержит ли строка подстроку в Bash
В Bash как проверить, начинается ли строка с определённого значения?
Извлечение подстроки в Bash
Как перебрать файлы в директории, изменить путь и добавить суффикс к имени файла
Почему сравнение строк с помощью '==' и 'is' иногда дает разные результаты?