28

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

28

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

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

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 ответ(ов)

6

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

basename filename .extension

Например:

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

В результате получите:

filename
0

Проблема, с которой вы столкнулись, связана с обработкой файлов, у которых нет расширения или имени файла. Ваш скрипт действительно справляется с большим количеством "парадоксальных" имён файлов, но есть некоторые нюансы, которые следует учесть.

Вот исправленный вариант вашего скрипта, который более корректно обрабатывает такие случаи:

#!/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  = ""

Этот скрипт теперь более устойчив к различным крайним случаям с именами файлов, что позволит корректно обрабатывать разнообразные ситуации.

0

Для извлечения имени файла и его расширения с помощью 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

Что касается работы команд, то:

  1. Команда для получения NAME использует замену, которая ищет символ ".", за которым следуют любые символы, кроме ".", до конца строки. Она заменяет найденную строку на пустую строку, то есть удаляет всё, начиная с последней точки и до конца строки. Это своего рода «негрековый» (non-greedy) шаблон.

  2. Команда для получения EXTENSION заменяет любое количество символов, заканчивающееся на символ ".", что находится в начале строки, на пустую строку. Это удаляет всё с начала строки до последней точки, включая её. Это «жадная» (greedy) замена, что является поведением по умолчанию для таких операций.

Таким образом, с помощью этих команд вы сможете легко извлекать имя файла и его расширение из полного имени файла.

0

В ответ на ваш вопрос, можно использовать следующие конструкции в Bash для работы с именами файлов:

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

file="thisfile.txt"
echo "имя файла: ${file%.*}"
echo "расширение: ${file##*.}"

Вывод будет таким:

имя файла: thisfile
расширение: txt

Таким образом, эти конструкции позволяют легко извлекать имя файла и его расширение из полного имени файла.

0

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

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

Чтобы ответить на вопрос, пожалуйста, войдите или зарегистрируйтесь