Pobieranie rozszerzenia z nazwy pliku

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

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

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

Wolałbym rozwiązanie wykorzystujące tylko bash bez zewnętrznego programów, jeśli jest to możliwe.

Aby wyjaśnić moje pytanie, tworzyłem skrypt bash do wypakowania dowolnego danego archiwum za pomocą jednego polecenia extract path_to_file. Jak wyodrębnienie pliku jest określane przez skrypt, sprawdzając jego typ kompresji lub archiwizacji, może to być .tar.gz, .gz, .bz2 itd. Myślę, że powinno to obejmować manipulację ciągiem znaków, na przykład jeśli otrzymam rozszerzenie .gz to powinienem sprawdzić, czy przed .gz ma ciąg .tar – jeśli tak, rozszerzenie powinno być .tar.gz.

Komentarze

  • file = ” /dir/subdir/file-1.0.tar.bz2

; echo $ {file ## *.} drukuje ' .bz2 ' tutaj. Czego oczekujesz '?

  • Potrzebuję .tar.bz2
  • Powiązane : Wyodrębnij nazwę pliku i rozszerzenie w Bash .
  • Odpowiedź

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

    Jak zdecydujesz, że chcesz, aby rozszerzenie było , a nie bz2 lub 0.tar.bz2? Najpierw musisz odpowiedzieć na to pytanie. Następnie możesz dowiedzieć się, co 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óry najlepiej traktować jako przypadek specjalny. Tutaj „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, musisz użyć instrukcja case do dopasowywania wzorców.

      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 potrzebne jest dalsze usuwanie. Tutaj „implementacja sa bash / ksh / zsh (wymagająca shopt -s extglob pod bash i setopt ksh_glob pod 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 traktuje to 0 jako rozszerzenie w file-1.0.gz.

    ¹ ${VARIABLE##SUFFIX} i powiązane konstrukcje znajdują się w POSIX , więc działają w dowolnej nie-antycznej powłoce w stylu Bournea, takiej jak ash, bash, ksh lub zsh.

    Komentarze

    • to powinno rozwiązać, sprawdzając, czy ciąg przed ostatnim tokenem . jest typem archiwum, na przykład tar, jeśli nie jest typem archiwalnym, takim jak 0 iteracja powinna się zakończyć.
    • @uray: to działa w tym konkretnym przypadku, ale ' nie jest rozwiązaniem ogólnym . Rozważ Maciej ' przykład .patch.lzma . Lepszy heur istic byłoby rozważenie ciągu po ostatnim .: if it ' sa sufiksem kompresji (.7z, .bz2, .gz, …), kontynuuj usuwanie.
    • @NoamM Co było nie tak z wcięciem? ' jest zdecydowanie uszkodzony po edycji: podwójnie zagnieżdżony kod jest wcięty tak samo, jak zagnieżdżony pojedynczo.

    Odpowiedź

    Możesz uprościć sprawę, wykonując po prostu dopasowanie do wzorca w nazwie pliku, zamiast dwukrotnie wyodrębniać rozszerzenie:

    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 

    Komentarze

    • To rozwiązanie jest bardzo proste.

    Odpowiedź

    $ echo "thisfile.txt"|awk -F . "{print $NF}" 

    Komentarze na ten temat tutaj: http://liquidat.wordpress.com/2007/09/29/short-tip-get-file-extension-in-shell-script/

    Komentarze

    • nie działa dla .tar.gz rozszerzenia
    • No a .tar .gz jest w rzeczywistości tar wewnątrz pliku gzip, więc działa w tym sensie, że usuwa rozszerzenie gz z pliku gzip.

    Odpowiedź

    Oto moja szansa: Przekształć kropki w znaki nowej linii, potokiem do tail, pobierz ostatnią linię:

    $> TEXT=123.234.345.456.456.567.678 $> echo $TEXT | tr . \\n | tail -n1 678 

    Odpowiedź

    Pewnego dnia stworzyłem te skomplikowane 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 wtedy, gdy działa o rozszerzeniach.

    Do sprawdzania rozszerzeń – To proste i niezawodne

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

    W przypadku odcięcia rozszerzenia:

    ~$ 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 uważasz, że są w pełni używane, zapoznaj się z tym postem 🙂 (i miejmy nadzieję, że umieść komentarz).

    Odpowiedź

    echo ${filename#$(echo $filename | sed "s/\.[^[:digit:]].*$//g;")} 

    Na przykład:

    Komentarze

    • Nie działa we wszystkich przypadkach. Spróbuj z ' foo.7z '
    • Potrzebujesz cudzysłowów i lepiej używaj printf w przypadku, gdy nazwa pliku zawiera ukośnik odwrotny lub zaczyna się od -: "${filename#$(printf %s "$filename" | sed 's/\.[^[:digit:]].*$//g;')}"
    • @axel_c : right, a ja ' zaimplementowałem tę samą specyfikację co Maciej jako przykład. Jaką heurystykę sugerujesz, że ' jest lepsze niż „zaczyna się od litery”?
    • @Gilles: po prostu myślę, że ' nie jest rozwiązaniem, chyba że używasz wstępnie obliczonej listy znanych rozszerzeń, ponieważ rozszerzenie może być dowolne.

    Odpowiedź

    odpowiedź jackmana oparta na wielkości liter jest całkiem dobra i przenośna, ale jeśli chcesz tylko nazwę pliku i rozszerzenie w zmiennej i”, 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 pierwsze musi być „tar”.

    Ale możesz zmienić wiersz testowy „tar” za pomocą testu długości łańcucha i powtórzyć poprawkę wiele razy .

    Odpowiedź

    Rozwiązałem to za pomocą tego:

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

    ale to działa tylko dla znanych typów archiwizacji, w tym przypadku tylko tar

    Dodaj komentarz

    Twój adres email nie zostanie opublikowany. Pola, których wypełnienie jest wymagane, są oznaczone symbolem *