Pobieranie rozszerzenia w nazwie pliku

33

Jak uzyskać rozszerzenie pliku z bash? Oto, co próbowałem:

filename=`basename $filepath`
fileext=${filename##*.}

W ten sposób mogę uzyskać rozszerzenie bz2ścieżki /dir/subdir/file.bz2, ale mam problem ze ścieżką /dir/subdir/file-1.0.tar.bz2.

Wolę rozwiązanie wykorzystujące tylko bash bez zewnętrznych programów, jeśli jest to możliwe.

Aby wyjaśnić moje pytanie, tworzyłem skrypt bash, aby wyodrębnić dowolne archiwum za pomocą jednego polecenia extract path_to_file. Sposób wyodrębnienia pliku jest określany przez skrypt na podstawie jego typu kompresji lub archiwizacji, którym mogą być .tar.gz, .gz, .bz2 itp. Myślę, że powinno to obejmować manipulację ciągami, na przykład jeśli otrzymam rozszerzenie, .gzto ja powinien sprawdzić, czy ma on .tarwcześniej ciąg .gz- jeśli tak, rozszerzenie powinno być .tar.gz.

uray
źródło
2
file = "/ dir / subdir / file-1.0.tar.bz2"; echo $ {plik ## *.} drukuje tutaj '.bz2'. Jakiej wydajności oczekujesz?
axel_c
1
Potrzebuję.tar.bz2
uray

Odpowiedzi:

19

Jeśli nazwa pliku to file-1.0.tar.bz2, rozszerzenie to bz2. Metoda używana do wyodrębnienia rozszerzenia ( fileext=${filename##*.}) jest całkowicie poprawna¹.

Jak zdecydować, że chcesz być rozszerzenie tar.bz2, a nie bz2czy 0.tar.bz2? Najpierw musisz odpowiedzieć na to pytanie. Następnie możesz dowiedzieć się, które polecenie powłoki pasuje do Twojej specyfikacji.

  • Jedną z możliwych specyfikacji jest to, że rozszerzenia muszą zaczynać się od litery. Ta heurystyka zawodzi w przypadku kilku popularnych rozszerzeń, takich jak 7z, które można najlepiej traktować jako szczególny przypadek. Oto implementacja bash / ksh / zsh:

    basename=$filename; fileext=
    while [[ $basename = ?*.* &&
             ( ${basename##*.} = [A-Za-z]* || ${basename##*.} = 7z ) ]]
    do
      fileext=${basename##*.}.$fileext
      basename=${basename%.*}
    done
    fileext=${fileext%.}

    Aby zapewnić przenośność POSIX, należy użyć caseinstrukcji do dopasowania wzorca.

    while case $basename in
            ?*.*) case ${basename##*.} in [A-Za-z]*|7z) true;; *) false;; esac;;
            *) false;;
          esac
    do 
  • Inną możliwą specyfikacją jest to, że niektóre rozszerzenia oznaczają kodowanie i wskazują, że konieczne jest dalsze usuwanie. Oto implementacja bash / ksh / zsh (wymagająca shopt -s extglobw bash i setopt ksh_globw zsh):

    basename=$filename
    fileext=
    while [[ $basename = ?*.@(bz2|gz|lzma) ]]; do
      fileext=${basename##*.}.$fileext
      basename=${basename%.*}
    done
    if [[ $basename = ?*.* ]]; then
      fileext=${basename##*.}.$fileext
      basename=${basename%.*}
    fi
    fileext=${fileext%.}

    Zauważ, że 0jest to rozszerzenie w file-1.0.gz.

¹ i pokrewne konstrukcje są w POSIX , więc działają one w dowolnej nie-antycznej powłoce w stylu Bourne'a, takiej jak ash, bash, ksh lub zsh. ${VARIABLE##SUFFIX}

Gilles „SO- przestań być zły”
źródło
należy to rozwiązać, sprawdzając, czy łańcuch przed ostatnim .tokenem jest typem archiwum, na przykład tar, czy jego typ 0niearchiwalny jak iteracja powinien się kończyć.
uray
2
@uray: działa w tym konkretnym przypadku, ale nie jest to ogólne rozwiązanie. Rozważ przykład Macieja.patch.lzma . Lepszym heurystyczny byłoby rozważyć ciąg po ostatnie .: czy jest to przyrostek kompresji ( .7z, .bz2, .gz, ...), nadal rozbiórki.
Gilles „SO- przestań być zły”
@NoamM Co było nie tak z wcięciem? Po edycji jest zdecydowanie zepsuty: kod podwójnie zagnieżdżony jest wcięty tak samo jak kod zagnieżdżony pojedynczo.
Gilles „SO- przestań być zły”
22

Możesz uprościć sprawy, po prostu dopasowując wzorce do nazwy pliku zamiast wyodrębnić rozszerzenie dwukrotnie:

case "$filename" in
    *.tar.bz2) bunzip_then_untar ;;
    *.bz2)     bunzip_only ;;
    *.tar.gz)  untar_with -z ;;
    *.tgz)     untar_with -z ;;
    *.gz)      gunzip_only ;;
    *.zip)     unzip ;;
    *.7z)      do something ;;
    *)         do nothing ;;
esac
Glenn Jackman
źródło
To rozwiązanie jest pięknie proste.
AsymLabs,
2

Oto moje ujęcie: Przetłumacz kropki na nowe linie, przeciągnij tail, uzyskaj ostatnią linię:

$> TEXT=123.234.345.456.456.567.678
$> echo $TEXT | tr . \\n | tail -n1
678
Michael Bar-Synaj
źródło
0
echo ${filename#$(echo $filename | sed 's/\.[^[:digit:]].*$//g;')}

Na przykład:

% echo $filename
2.6.35-zen2.patch.lzma
% echo ${filename#$(echo $filename | sed 's/\.[^[:digit:]].*$//g;')}
.patch.lzma
Maciej Piechotka
źródło
Nie działa we wszystkich przypadkach. Spróbuj z „foo.7z”
axel_c
Potrzebujesz cytatów i lepiej użyj, printfjeśli nazwa pliku zawiera odwrotny ukośnik lub zaczyna się od -:"${filename#$(printf %s "$filename" | sed 's/\.[^[:digit:]].*$//g;')}"
Gilles 'SO- przestań być zły'
@axel_c: racja, a ja zaimplementowałem taką samą specyfikację jak Maciej jako przykład. Jaka według ciebie heurystyka jest lepsza niż „zaczyna się na literę”?
Gilles „SO- przestań być zły”
1
@Gilles: Myślę, że nie ma rozwiązania, chyba że użyjesz wstępnie obliczonej listy znanych rozszerzeń, ponieważ rozszerzenie może być dowolne.
axel_c
0

Pewnego dnia stworzyłem te trudne funkcje:

# args: string how_many
function get_last_letters(){ echo ${1:${#1}-$2:$2}; }
function cut_last_letters(){ echo ${1:0:${#1}-$2}; }

Znalazłem to proste podejście, bardzo przydatne w wielu przypadkach, nie tylko w przypadku rozszerzeń.

Do sprawdzania rozszerzeń - to proste i niezawodne

~$ get_last_letters file.bz2 4
.bz2
~$ get_last_letters file.0.tar.bz2 4
.bz2

Do odcinania przedłużenia:

~$ cut_last_letters file.0.tar.bz2 4
file.0.tar

Aby zmienić rozszerzenie:

~$ echo $(cut_last_letters file.0.tar.bz2 4).gz
file.0.tar.gz

Lub jeśli lubisz "przydatne funkcje:

~$ function cut_last_letters_and_add(){ echo ${1:0:${#1}-$2}"$3"; }
~$ cut_last_letters_and_add file.0.tar.bz2 4 .gz
file.0.tar.gz

PS Jeśli podobały Ci się te funkcje lub okazały się przydatne, zapoznaj się z tym postem :) (i mam nadzieję, że skomentujesz).

Grzegorz Wierzowiecki
źródło
0

Odpowiedź oparta na wielkości liter jest bardzo dobra i przenośna, ale jeśli chcesz tylko nazwę pliku i rozszerzenie w zmiennej, znalazłem to rozwiązanie:

INPUTFILE="$1"
INPUTFILEEXT=$( echo -n "$INPUTFILE" | rev | cut -d'.' -f1 | rev )
INPUTFILEEXT=$( echo -n $INPUTFILEEXT | tr '[A-Z]' '[a-z]' ) # force lowercase extension
INPUTFILENAME="`echo -n \"$INPUTFILE\" | rev | cut -d'.' -f2- | rev`"

# fix for files with multiple extensions like "gbamidi-v1.0.tar.gz"
INPUTFILEEXT2=$( echo -n "$INPUTFILENAME" | rev | cut -d'.' -f1 | rev )
if [ "$INPUTFILEEXT2" = "tar" ]; then
    # concatenate the extension
    INPUTFILEEXT="$INPUTFILEEXT2.$INPUTFILEEXT"
    # update the filename
    INPUTFILENAME="`echo -n \"$INPUTFILENAME\" | rev | cut -d'.' -f2- | rev`"
fi

Działa tylko z podwójnymi rozszerzeniami i pierwszym musi być „tar”.

Możesz jednak zmienić linię testową „tar” za pomocą testu długości łańcucha i powtórzyć poprawkę wiele razy.

eadmaster
źródło
-1

rozwiązałem to za pomocą:

filename=`basename $filepath`
fileext=${filename##*.}
fileext2=${filename%.*}
fileext3=${fileext2##*.}
if [ "$fileext3" == "tar" ]; then
    fileext="tar."$fileext
fi

ale działa to tylko dla znanego typu archiwizacji, tylko w tym przypadku tar

uray
źródło