Ergreifen der Erweiterung in einem Dateinamen


33

Wie bekomme ich die Dateierweiterung von Bash? Folgendes habe ich versucht:

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

Auf diese Weise kann ich eine Erweiterung des bz2Pfads erhalten /dir/subdir/file.bz2, aber ich habe ein Problem mit dem Pfad /dir/subdir/file-1.0.tar.bz2.

Ich würde eine Lösung vorziehen, die nur Bash ohne externe Programme verwendet, wenn es möglich ist.

Um meine Frage zu klären, habe ich ein Bash-Skript erstellt, um ein bestimmtes Archiv mit einem einzigen Befehl von zu extrahieren extract path_to_file. Wie die Datei extrahiert wird, bestimmt das Skript anhand des Komprimierungs- oder Archivierungstyps, der .tar.gz, .gz, .bz2 usw. sein kann. Ich denke, dies sollte Zeichenfolgenmanipulation beinhalten, z. B. wenn ich die Erweiterung bekomme, .gzdann habe ich sollte .tarvorher prüfen, ob es die Zeichenfolge hat .gz- wenn ja, sollte die Erweiterung sein .tar.gz.


2
file = "/ dir / subdir / file-1.0.tar.bz2"; echo $ {file ## *.} gibt hier '.bz2' aus. Welche Ausgabe erwarten Sie?
axel_c

1
Ich brauche.tar.bz2
uray

Antworten:


19

Wenn der Dateiname lautet file-1.0.tar.bz2, lautet die Erweiterung bz2. Die Methode, mit der Sie die Erweiterung ( fileext=${filename##*.}) extrahieren, ist vollkommen gültig¹.

Wie entscheiden Sie, ob Sie die Erweiterung haben möchten oder tar.bz2nicht ? Sie müssen diese Frage zuerst beantworten. Dann können Sie herausfinden, welcher Shell-Befehl Ihrer Spezifikation entspricht.bz20.tar.bz2

  • Eine mögliche Spezifikation ist, dass Erweiterungen mit einem Buchstaben beginnen müssen. Diese Heuristik schlägt für ein paar gängige Erweiterungen wie fehl 7z, die am besten als Sonderfall behandelt werden könnten. Hier ist eine bash / ksh / zsh-Implementierung:

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

    Für die POSIX-Portabilität müssen Sie eine caseAnweisung für den Mustervergleich verwenden.

    while case $basename in
            ?*.*) case ${basename##*.} in [A-Za-z]*|7z) true;; *) false;; esac;;
            *) false;;
          esac
    do 
  • Eine weitere mögliche Spezifikation besteht darin, dass einige Erweiterungen Codierungen bezeichnen und anzeigen, dass weiteres Abisolieren erforderlich ist. Hier ist eine bash / ksh / zsh-Implementierung (erfordert shopt -s extglobunder bash und setopt ksh_globunder 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%.}

    Beachten Sie, dass dies 0eine Erweiterung von ist file-1.0.gz.

¹ und verwandte Konstrukte sind in POSIX enthalten , daher funktionieren sie in jeder nicht-antiken Bourne-Shell wie z. B. ash, bash, ksh oder zsh. ${VARIABLE##SUFFIX}


Dies sollte gelöst werden, indem überprüft wird, ob der String vor dem letzten .Token ein Archivtyp ist, z. B. tarob sein nicht-Archivtyp wie die 0Iteration enden sollte.
Uray

2
@uray: das funktioniert in diesem speziellen Fall, aber es ist keine allgemeine Lösung. Betrachten Sie Maciejs Beispiel von.patch.lzma . Eine bessere Heuristik die Zeichenfolge zu betrachten wäre nach dem letzten .: Wenn es sich um eine Kompression Suffix ist ( .7z, .bz2, .gz, ...), weiter Strippen.
Gilles 'SO - hör auf böse zu sein'

@NoamM Was war mit dem Einzug falsch? Es ist definitiv kaputt nach der Bearbeitung: doppelt verschachtelter Code wird genauso eingerückt wie einfach verschachtelter Code.
Gilles 'SO - hör auf böse zu sein'

22

Sie können die Sache vereinfachen, indem Sie nur einen Mustervergleich für den Dateinamen durchführen, anstatt die Erweiterung zweimal zu extrahieren:

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

Diese Lösung ist sehr einfach.
AsymLabs


2

Hier ist mein Versuch: Punkte in Zeilenumbrüche übersetzen, durchkreuzen, tailletzte Zeile holen:

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

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

Beispielsweise:

% echo $filename
2.6.35-zen2.patch.lzma
% echo ${filename#$(echo $filename | sed 's/\.[^[:digit:]].*$//g;')}
.patch.lzma

Funktioniert nicht in allen Fällen. Versuchen Sie es mit 'foo.7z'
axel_c

Sie benötigen Anführungszeichen und sollten diese besser verwenden, printfwenn der Dateiname einen umgekehrten Schrägstrich enthält oder mit Folgendem beginnt -:"${filename#$(printf %s "$filename" | sed 's/\.[^[:digit:]].*$//g;')}"
Gilles 'SO

@axel_c: richtig, und ich habe die gleiche Spezifikation wie Maciej als Beispiel implementiert. Welche Heuristik ist Ihrer Meinung nach besser als "beginnt mit einem Buchstaben"?
Gilles 'SO - hör auf böse zu sein'

1
@ Gilles: Ich denke, es gibt keine Lösung, es sei denn, Sie verwenden eine vorberechnete Liste bekannter Erweiterungen, weil eine Erweiterung alles sein kann.
axel_c

0

Eines Tages habe ich diese kniffligen Funktionen erstellt:

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

Ich habe diesen einfachen Ansatz gefunden, der in vielen Fällen sehr nützlich ist, nicht nur, wenn es um Erweiterungen geht.

Zum Überprüfen von Erweiterungen - Es ist einfach und zuverlässig

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

Zum Abschneiden der Verlängerung:

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

Zum Ändern der Erweiterung:

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

Oder wenn Sie "praktische Funktionen" mögen:

~$ 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 Wenn Ihnen diese Funktionen gefallen haben oder Sie sie als gebraucht empfanden, lesen Sie bitte diesen Beitrag :) (und geben Sie hoffentlich einen Kommentar ab).


0

Die jackman case-based Antwort ist ziemlich gut und portabel, aber wenn Sie nur den Dateinamen und die Erweiterung in einer Variablen wollen, habe ich diese Lösung gefunden:

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

Es funktioniert nur mit doppelten Erweiterungen und die erste muss "tar" sein.

Sie können jedoch die Testlinie "tar" mit einem Stringlängentest ändern und den Fix mehrmals wiederholen.


-1

Ich habe es folgendermaßen gelöst:

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

Dies funktioniert jedoch nur für bekannte Archivierungsarten, in diesem Fall nur tar

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.