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 '?
.tar.bz2
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 isetopt 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 wfile-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ładtar
, jeśli nie jest typem archiwalnym, takim jak0
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