Как извлечь имя файла и расширение отдельно в Bash, когда имя файла содержит несколько точек?
Я хочу получить имя файла (без расширения) и расширение отдельно в 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 только для этой задачи, если это возможно.
Какие есть лучшие подходы для правильного извлечения имени файла и расширения в Bash, особенно для имен файлов с несколькими точками?
Самый надежный способ извлечь имя файла и расширение в Bash при работе с несколькими точками — использовать расширение параметров с удалением шаблонов, которое правильно определяет расширение как все после крайней правой точки. Самое простое решение:
FILENAME="${FILE%.*}"
EXTENSION="${FILE##*.}"
Это правильно обработает имена файлов с несколькими точками, давая вам a.b как имя файла и js как расширение для a.b.js.
Содержание
- Понимание проблемы
- Решения с расширением параметров
- Альтернативные подходы
- Сравнение методов
- Особые случаи и соображения
- Заключение
Понимание проблемы
Основная проблема подхода на основе cut заключается в том, что он рассматривает точки как разделители и разбивает имя файла на несколько частей. Когда имя файла содержит несколько точек, например document.v1.2.txt, метод cut -d'.' -f1 захватывает только первую часть (document), а cut -d'.' -f2 захватывает вторую часть (v1.2), оставляя вам неполную картину.
Правильный подход, как реализованный в Python
os.path.splitext(), рассматривает расширение как все после последней точки, а не после первой.
Это означает:
- Для
filename.tar.gz→ имя файла:filename.tar, расширение:gz - Для
archive.2024.01.zip→ имя файла:archive.2024.01, расширение:zip - Для
config(без точек) → имя файла:config, расширение: пустая строка - Для
.hidden(начинается с точки) → имя файла: пустая строка, расширение:hidden
Решения с расширением параметров
Базовое расширение параметров
Самый элегантный и эффективный способ использует встроенное расширение параметров Bash:
#!/bin/bash
FILE="a.b.js"
FILENAME="${FILE%.*}" # Удаляет самый короткий шаблон с конца
EXTENSION="${FILE##*.}" # Удаляет самый длинный шаблон с начала
echo "Filename: $FILENAME" # Вывод: a.b
echo "Extension: $EXTENSION" # Вывод: js
Как это работает:
${FILE%.*}удаляет самое короткое совпадающее шаблон с конца (все после последней точки)${FILE##*.}удаляет самое длинное совпадающее шаблон с начала (все до последней точки)
Пример полного скрипта
#!/bin/bash
extract_parts() {
local file="$1"
local filename="${file%.*}"
local extension="${file##*.}"
# Обработка случая, когда нет расширения
if [ "$filename" = "$file" ]; then
extension=""
fi
echo "Filename: '$filename'"
echo "Extension: '$extension'"
}
# Тестовые случаи
extract_parts "a.b.js" # Filename: 'a.b', Extension: 'js'
extract_parts "document.tar.gz" # Filename: 'document.tar', Extension: 'gz'
extract_parts "config" # Filename: 'config', Extension: ''
extract_parts ".hidden" # Filename: '', Extension: 'hidden'
extract_parts "..test.." # Filename: '..test.', Extension: ''
Альтернативные подходы
Использование basename и dirname
Хотя basename и dirname в основном используются для манипуляции путями, их можно комбинировать для получения результата:
#!/bin/bash
FILE="path/to/file.name.with.dots.txt"
BASENAME=$(basename "$FILE")
FILENAME="${BASENAME%.*}"
EXTENSION="${BASENAME##*.}"
echo "Filename: $FILENAME" # Вывод: file.name.with.dots
echo "Extension: $EXTENSION" # Вывод: txt
Использование awk
Решение с awk предоставляет альтернативный подход:
#!/bin/bash
FILE="a.b.c.d.e"
FILENAME=$(echo "$FILE" | awk -F'.' '{$NF=""; gsub(/\.$/, ""); print}')
EXTENSION=$(echo "$FILE" | awk -F'.' '{print $NF}')
echo "Filename: $FILENAME" # Вывод: a.b.c.d
echo "Extension: $EXTENSION" # Вывод: e
Использование sed
Для тех, кто предпочитает sed, вот решение:
#!/bin/bash
FILE="document.v1.2.txt"
FILENAME=$(echo "$FILE" | sed 's/\.[^.]*$//')
EXTENSION=$(echo "$FILE" | sed 's/^.*\.//')
echo "Filename: $FILENAME" # Вывод: document.v1.2
echo "Extension: $EXTENSION" # Вывод: txt
Использование rev и cut
Этот творческий подход переворачивает строку, режет, а затем переворачивает обратно:
#!/bin/bash
FILE="archive.2024.01.zip"
EXTENSION=$(echo "$FILE" | rev | cut -d'.' -f1 | rev)
FILENAME="${FILE%.*}"
echo "Filename: $FILENAME" # Вывод: archive.2024.01
echo "Extension: $EXTENSION" # Вывод: zip
Сравнение методов
| Метод | Производительность | Читаемость | Портативность | Особые случаи |
|---|---|---|---|---|
| Расширение параметров | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | Отлично |
basename + %.* |
⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | Очень хорошо |
awk |
⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐⭐⭐ | Хорошо |
sed |
⭐⭐⭐ | ⭐⭐ | ⭐⭐⭐⭐ | Хорошо |
rev + cut |
⭐⭐ | ⭐⭐ | ⭐⭐⭐ | Удовлетворительно |
Расширение параметров побеждает по всем фронтам:
- Самый быстрый: Не вызываются внешние процессы
- Самый чистый: Встроенный синтаксис оболочки
- Самый портативный: Работает во всех современных оболочках
- Самый надежный: Хорошо обрабатывает все особые случаи
Особые случаи и соображения
Имена файлов без расширений
FILE="config"
FILENAME="${FILE%.*}" # config
EXTENSION="${FILE##*.}" # config (проблема - то же, что и имя файла)
Решение: Проверьте, одинаковы ли они:
if [ "$FILENAME" = "$FILE" ]; then
EXTENSION=""
fi
Скрытые файлы (начинающиеся с точки)
FILE=".bashrc"
FILENAME="${FILE%.*}" # пустая строка
EXTENSION="${FILE##*.}" # bashrc
Файлы, заканчивающиеся точкой
FILE="file."
FILENAME="${FILE%.*}" # file
EXTENSION="${FILE##*.}" # пустая строка
Файлы, состоящие только из точек
FILE=".."
FILENAME="${FILE%.*}" # пустая строка
EXTENSION="${FILE##*.}" # пустая строка
Полная надежная функция
#!/bin/bash
extract_file_parts() {
local file="$1"
local filename="${file%.*}"
local extension="${file##*.}"
# Обработка специальных случаев
if [ "$filename" = "$file" ]; then
extension=""
elif [ -z "$filename" ] && [ -n "$extension" ]; then
# Скрытый файл, как .bashrc
filename=".$extension"
extension=""
fi
printf "Filename: '%s'\n" "$filename"
printf "Extension: '%s'\n" "$extension"
}
# Тестирование всех особых случаев
extract_file_parts "a.b.js" # Обычный случай
extract_file_parts "document.tar.gz" # Несколько точек
extract_file_parts "config" # Без расширения
extract_file_parts ".hidden" # Скрытый файл
extract_file_parts "file." # Заканчивается точкой
extract_file_parts ".." # Только точки
Заключение
Расширение параметров с ${FILE%.*} и ${FILE##*.} — лучший подход для извлечения имени файла и расширения в Bash при работе с несколькими точками. Этот метод:
- Самый эффективный — не вызываются внешние процессы
- Правильно обрабатывает все особые случаи, включая скрытые файлы и файлы без расширений
- Работает во всех современных оболочках (Bash, Zsh, Dash и т.д.)
- Сохраняет читаемость, как только вы понимаете синтаксис
- Самый надежный для производственных скриптов
Для надежного написания скриптов всегда включайте проверки для имен файлов без расширений и скрытых файлов. Подход с расширением параметров превосходит альтернативы, такие как cut, awk, sed и rev+cut, по производительности, надежности и портативности, сохраняя отличную читаемость.
При работе с путями файлов, включающими каталоги, комбинируйте basename с расширением параметров: FILENAME="${basename%.*}", чтобы убедиться, что вы работаете только с частью имени файла.
Этот подход дает вам функциональность Python os.path.splitext() с использованием только встроенных возможностей оболочки, устраняя необходимость вызывать внешние интерпретаторы для простых задач разбора имен файлов.