Pytanie Wyodrębnij nazwę pliku i rozszerzenie w Bash


Chcę uzyskać nazwę pliku (bez rozszerzenia) i rozszerzenie osobno.

Najlepszym rozwiązaniem, jakie znalazłem do tej pory, jest:

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

Jest to błędne, ponieważ nie działa, jeśli nazwa pliku zawiera wiele . postacie. Jeśli, powiedzmy, mam a.b.js, to rozważy a i b.js, zamiast a.b i js.

Można to łatwo zrobić w Pythonie z

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

ale wolałbym nie uruchamiać interpretera Pythona tylko w tym celu, jeśli to możliwe.

Jakieś lepsze pomysły?


1620
2018-06-08 14:00


pochodzenie


To pytanie wyjaśnia tę technikę bash i kilka innych powiązanych. - jjclarkson
Gdy stosujesz świetne odpowiedzi poniżej, nie wklejaj po prostu swojej zmiennej, tak jak tutaj pokazuję Źle:  extension="{$filename##*.}" tak jak ja przez jakiś czas! Przesuń $ poza kręconymi: Dobrze:  extension="${filename##*.}" - Chris K
Jest to niewątpliwie niebagatelny problem i dla mnie trudno stwierdzić, czy poniższe odpowiedzi są całkowicie poprawne. To niesamowite, że nie jest to wbudowana operacja w (ba) sh (odpowiedzi wydają się implementować funkcję za pomocą dopasowywania wzorców). Postanowiłem użyć Pythona os.path.splitext jak wyżej zamiast ... - Peter Gibson
Uzgodnione - ten sam komentarz. Czy nie ma dostępnego skompilowanego / nieobsługiwanego narzędzia, które pobiera przełączniki wiersza poleceń w niezliczonych przypadkach użycia tego - np. zamiast tego otrzymuje rozszerzenie lub ostatnie n. ograniczniki lub sprawdzanie typu pliku w nagłówku binarnym itp. .? - Alex Hall
Tak jak rozbudowa muszą reprezentować Natura pliku, istnieje magia polecenie, które sprawdza plik, aby odgadnąć jego charakter i ofertę standardowe rozszerzenie. widzieć moja odpowiedź - F. Hauri


Odpowiedzi:


Najpierw pobierz nazwę pliku bez ścieżki:

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

Alternatywnie możesz skupić się na ostatnim "/" ścieżki zamiast "." który powinien działać, nawet jeśli masz nieprzewidywalne rozszerzenia plików:

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

2815
2018-06-08 14:05



Sprawdzić gnu.org/software/bash/manual/html_node/... dla pełnego zestawu funkcji. - D.Shawley
Dodaj kilka cudzysłowów do "$ fullfile" lub ryzykujesz zerwanie nazwy pliku. - lhunath
Heck, możesz nawet napisać filename = "$ {fullfile ## * /}" i unikać wywoływania dodatkowych basename - ephemient
To "rozwiązanie" nie działa, jeśli plik nie ma rozszerzenia - zamiast tego wypisywana jest cała nazwa pliku, co jest dość złe, biorąc pod uwagę, że pliki bez rozszerzeń są wszechobecne. - nccc
Naprawiono obsługę nazw plików bez rozszerzenia: extension=$([[ "$filename" = *.* ]] && echo ".${filename##*.}" || echo ''). Zauważ, że jeśli rozszerzenie jest obecny, zostanie zwrócony wraz z początkowym .np. .txt. - mklement0


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

Aby uzyskać więcej informacji, zobacz rozszerzenie parametrów powłoki w instrukcji Bash.


503
2018-06-08 14:05



Ty (może nieświadomie) przywołasz doskonałe pytanie, co zrobić, jeśli część "rozszerzenia" nazwy pliku ma 2 kropki, tak jak w .tar.gz ... Nigdy nie brałem pod uwagę tego problemu i podejrzewam, że to jest nie można go rozwiązać bez znajomości wszystkich możliwych poprawnych rozszerzeń plików z góry. - rmeador
Dlaczego nie można go rozwiązać? W moim przykładzie należy uznać, że plik zawiera dwa rozszerzenia, a nie rozszerzenie z dwoma kropkami. Obsługujesz oba rozszerzenia osobno. - Juliano
Jest nierozwiązywalny na podstawie leksykalnej, musisz sprawdzić typ pliku. Zastanów się, czy gra nazywała się dinosaurs.in.tar i gzip go dinosaurs.in.tar.gz :) - porges
Staje się to bardziej skomplikowane, jeśli przechodzisz na pełne ścieżki. Jedna z moich miała "." w katalogu na środku ścieżki, ale brak w nazwie pliku. Przykład "a / b.c / d / e / filename" zakończyłby się ".c / d / e / filename" - Walt Sellers
wyraźnie nie x.tar.gzjest rozszerzenie gz a nazwa pliku to x.tar to jest to. Nie ma czegoś takiego jak podwójne rozszerzenia. Jestem pewien, że boost :: filesystem obsługuje to w ten sposób. (split path, change_extension ...) i jego zachowanie jest oparte na pythonie, jeśli się nie mylę. - v.oddou


Zazwyczaj znasz już rozszerzenie, więc możesz chcieć użyć:

basename filename .extension

na przykład:

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

i dostajemy

filename

281
2017-10-19 10:56



Ten drugi argument do basename jest dość otwieraczem oczu, ty dobry panie / madam :) - akaIDIOT
A jak wyodrębnić rozszerzenie za pomocą tej techniki? ;) Zaczekaj! Właściwie to nie wiemy z góry. - Tomasz Gandor
Powiedzmy, że masz spakowany katalog, który kończy się na .zip lub .ZIP. Czy jest jakiś sposób na zrobienie czegoś takiego basename $file {.zip,.ZIP}? - Dennis
Chociaż odpowiada to tylko na część pytania OP, odpowiada na pytanie wpisane w google. :-) Bardzo śliskie! - sudo make install


Możesz użyć magii zmiennych POSIX:

bash-3.2$ FILENAME=somefile.tar.gz
bash-3.2$ echo ${FILENAME%%.*}
somefile
bash-3.2$ echo ${FILENAME%.*}
somefile.tar

Istnieje zastrzeżenie, że jeśli nazwa pliku pochodzi z formularza ./somefile.tar.gz następnie echo ${FILENAME%%.*} chciwie usunie najdłuższy mecz z . i będziesz miał pusty ciąg.

(Możesz obejść to za pomocą zmiennej tymczasowej:

FULL_FILENAME=$FILENAME
FILENAME=${FULL_FILENAME##*/}
echo ${FILENAME%%.*}

)


To teren wyjaśnia więcej.

${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
2018-02-05 09:09



O wiele prostsze niż odpowiedź Joachima, ale zawsze muszę sprawdzić substytucję zmienną POSIX. Ponadto działa na Max OSX gdzie cut nie ma --complement i sed nie ma -r. - jwadsack


Wydaje się, że nie działa, jeśli plik nie ma rozszerzenia lub nie ma nazwy pliku. Oto, czego używam; używa tylko wbudowanych i obsługuje więcej (ale nie wszystkie) patologicznych nazw plików.

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

A oto kilka przykładów testowych:

$ 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 = ""

65
2017-09-10 05:17



Zamiast dir="${fullpath:0:${#fullpath} - ${#filename}}" Często widziałem dir="${fullpath%$filename}". Łatwiej jest pisać. Nie jestem pewien, czy istnieje jakakolwiek realna różnica prędkości lub pułapki. - dubiousjim
Wykorzystuje to #! / Bin / bash, który prawie zawsze jest nieprawidłowy. Preferuj #! / Bin / sh, jeśli to możliwe, lub #! / Usr / bin / env bash, jeśli nie. - Good Person
@Dobra osoba: Nie wiem, jak to prawie zawsze jest złe: which bash -> /bin/bash ; może to twoja dystrybucja? - vol7ron
@ vol7ron - na wielu dystrybucjach bash znajduje się w / usr / local / bin / bash. Na OSX wiele osób instaluje zaktualizowane bash w / opt / local / bin / bash. Jako takie / bin / bash jest źle i należy użyć env, aby go znaleźć. Jeszcze lepiej jest użyć konstrukcji / bin / sh i POSIX. Z wyjątkiem solaris jest to powłoka POSIX. - Good Person
@GoodPerson, ale jeśli czujesz się lepiej w bashu, dlaczego używasz sh? Czy nie jest tak, jak mówienie, po co używać Perla, kiedy możesz używać sh? - vol7ron


Możesz użyć basename.

Przykład:

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

Musisz podać systemame z rozszerzeniem, które powinno zostać usunięte, jednak jeśli zawsze wykonujesz polecenie tar z -z wtedy wiesz, że rozszerzenie będzie .tar.gz.

Powinno to zrobić, co chcesz:

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

40
2018-02-05 08:50



Przypuszczam cd $(basename $1 .tar.gz) działa dla plików .gz. Ale o którym wspomniał Archive files have several extensions: tar.gz, tat.xz, tar.bz2 - SS Hegde
Tomi Po pisał te same rzeczy dwa lata wcześniej. - Blauhirn
Cześć Blauhirn, bo to stare pytanie. Myślę, że coś się stało z datami. Wyraźnie pamiętam, że odpowiadałem na to pytanie krótko po tym, jak został poproszony, a tam, gdzie tylko kilka innych odpowiedzi. Czy to możliwe, że pytanie zostało połączone z innym, czy SO to robi? - Bjarke Freund-Hansen
Tak, dobrze pamiętam. Pierwotnie udzielam odpowiedzi na to pytanie stackoverflow.com/questions/14703318/... Tego samego dnia, kiedy został poproszony, dwa lata później został połączony w jeden. Nie można mnie winić za duplikat odpowiedzi, gdy moja odpowiedź została przeniesiona w ten sposób. - Bjarke Freund-Hansen


Mellen pisze w komentarzu do posta na blogu:

Korzystając z Basha, istnieje również ${file%.*} aby uzyskać nazwę pliku bez rozszerzenia i ${file##*.} aby uzyskać samo rozszerzenie. To jest,

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

Wyjścia:

filename: thisfile
extension: txt

24
2017-07-21 10:24



Jak dokładnie działa ta składnia? - syntagma
@REACHUS: Zobacz gnu.org/software/bash/manual/html_node/... - mklement0


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

działa dobrze, więc możesz po prostu użyć:

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

Polecenia, nawiasem mówiąc, działają w następujący sposób.

Polecenie dla NAME zastępuje a "." znak, po którym następuje dowolna liczba"." znaki do końca linii, bez niczego (to znaczy, usuwa wszystko z finału "."do końca linii, włącznie). Zasadniczo jest to substytucja non-chciwa z użyciem sztuczek regex.

Polecenie dla EXTENSION zastępuje dowolną liczbę znaków, po których następuje a "." znak na początku linii, bez niczego (tj. usuwa wszystko od początku linii do końcowej kropki, włącznie). Jest to chciwa podstawienie, które jest domyślną akcją.


24
2018-06-08 14:14





Możesz użyć cut polecenie, aby usunąć dwa ostatnie rozszerzenia ( ".tar.gz" część):

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

Jak zauważył Clayton Hughes w komentarzu, nie będzie to działać dla rzeczywistego przykładu w pytaniu. Jako alternatywę proponuję użyć sed z rozszerzonymi wyrażeniami regularnymi, takimi jak to:

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

Działa bezwarunkowo, usuwając dwa ostatnie (alfanumeryczne) rozszerzenia.

[Aktualizacja po komentarzu Andersa Lindahla]


22
2018-02-05 08:53



Działa to tylko w przypadku, gdy nazwa pliku / ścieżka nie zawiera żadnych innych kropek: echo "mpc-1.0.1.tar.gz" | cut -d "." --complement -f2- tworzy "mpc-1" (tylko pierwsze 2 pola po ograniczeniu o.) - Clayton Hughes
@ ClaytonHughes Masz rację, i powinienem przetestować to lepiej. Dodano inne rozwiązanie. - Some programmer dude
Powinno używać się wyrażeń sed $ aby sprawdzić, czy dopasowane rozszerzenie znajduje się na końcu nazwy pliku. W przeciwnym razie plik taki jak i.like.tar.gz.files.tar.bz2 może spowodować nieoczekiwany wynik. - Anders Lindahl
@AndersLindahl Nadal będzie, jeśli kolejność rozszerzeń jest odwrotnością sed kolejność łańcuchów. Nawet z $ na końcu nazwy pliku, na przykład mpc-1.0.1.tar.bz2.tar.gz usunie oba .tar.gz i wtedy .tar.bz2. - Some programmer dude


Oto kilka alternatywnych propozycji (głównie w awk), w tym niektóre zaawansowane przypadki użycia, takie jak wyodrębnianie numerów wersji dla pakietów oprogramowania.

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 '^[/]\|^~/'

Wszystkie przypadki użycia używają oryginalnej pełnej ścieżki jako danych wejściowych, bez zależności od wyników pośrednich.


19
2018-06-16 09:02