Wie entferne ich das Dateisuffix und den Pfadabschnitt aus einer Pfadzeichenfolge in Bash?

396

/foo/fizzbuzz.barWie würde ich bei einem String-Dateipfad wie z. B. bash verwenden, um nur den fizzbuzzTeil des Strings zu extrahieren ?

Lawrence Johnston
quelle
Informationen können Sie in finden Bash Handbuch , sucht ${parameter%word}und ${parameter%%word}in Hinterteil Anpassungsabschnitt.
1ac0

Antworten:

574

Hier erfahren Sie, wie Sie dies mit den Operatoren # und% in Bash tun.

$ x="/foo/fizzbuzz.bar"
$ y=${x%.bar}
$ echo ${y##*/}
fizzbuzz

${x%.bar}könnte auch sein ${x%.*}, alles nach einem Punkt ${x%%.*}zu entfernen oder alles nach dem ersten Punkt zu entfernen.

Beispiel:

$ x="/foo/fizzbuzz.bar.quux"
$ y=${x%.*}
$ echo $y
/foo/fizzbuzz.bar
$ y=${x%%.*}
$ echo $y
/foo/fizzbuzz

Die Dokumentation finden Sie im Bash-Handbuch . Sucht ${parameter%word}und ${parameter%%word}Hinterteil Anpassungsabschnitt.

Zan Lynx
quelle
Am Ende habe ich dieses verwendet, weil es am flexibelsten war und es noch ein paar ähnliche Dinge gab, die ich auch tun wollte, damit dies gut funktioniert.
Lawrence Johnston
Dies ist wahrscheinlich die flexibelste aller veröffentlichten Antworten, aber ich denke, dass die Antworten, die die Befehle basename und dirname vorschlagen, ebenfalls einige Aufmerksamkeit verdienen. Sie können nur der Trick sein, wenn Sie keinen anderen ausgefallenen Musterabgleich benötigen.
mgadda
7
Wie heißt das $ {x% .bar}? Ich würde gerne mehr darüber erfahren.
Basil
14
@Basil: Parametererweiterung. Geben Sie auf einer Konsole "man bash" und dann "/ parameter extension" ein
Zan Lynx
1
Ich denke, die "Man Bash" -Erklärung ist sinnvoll, wenn Sie bereits wissen, was es tut, oder wenn Sie es selbst auf die harte Tour ausprobiert haben. Es ist fast so schlimm wie Git Referenz. Ich würde es stattdessen einfach googeln.
Triplebig
226

Schauen Sie sich den Befehl basename an:

NAME="$(basename /foo/fizzbuzz.bar .bar)"
Zickdon
quelle
11
Wahrscheinlich die einfachste aller derzeit angebotenen Lösungen ... obwohl ich $ (...) anstelle von Backticks verwenden würde.
Michael Johnson
6
Am einfachsten, fügt aber eine Abhängigkeit hinzu (keine große oder seltsame, gebe ich zu). Es muss auch das Suffix kennen.
Vinko Vrsalovic
Und kann verwendet werden, um alles vom Ende zu entfernen. Im Grunde genommen wird nur eine Zeichenfolge vom Ende entfernt.
Smar
2
Das Problem ist der Zeitschlag. Ich habe gerade die Frage nach dieser Diskussion durchsucht, nachdem Bash fast 5 Minuten gebraucht hatte, um 800 Dateien mit dem Basisnamen zu verarbeiten. Unter Verwendung der obigen Regex-Methode wurde die Zeit auf etwa 7 Sekunden reduziert. Obwohl diese Antwort für den Programmierer einfacher durchzuführen ist, ist der Zeitaufwand einfach zu groß. Stellen Sie sich einen Ordner mit ein paar tausend Dateien vor! Ich habe einige solche Ordner.
Xizdaqrian
@ xizdaqrian Das ist absolut falsch. Dies ist ein einfaches Programm, dessen Rückkehr keine halbe Sekunde dauern sollte. Ich habe gerade time find / home / me / dev -name "* .py" .py -exec basename {} \; und es entfernte die Erweiterung und das Verzeichnis für 1500 Dateien in insgesamt 1 Sekunde.
Laszlo Treszkai
40

Pure Bash, durchgeführt in zwei getrennten Operationen:

  1. Entfernen Sie den Pfad aus einer Pfadzeichenfolge:

    path=/foo/bar/bim/baz/file.gif
    
    file=${path##*/}  
    #$file is now 'file.gif'
  2. Entfernen Sie die Erweiterung aus einer Pfadzeichenfolge:

    base=${file%.*}
    #${base} is now 'file'.
Param
quelle
18

Mit dem Basisnamen habe ich Folgendes verwendet, um dies zu erreichen:

for file in *; do
    ext=${file##*.}
    fname=`basename $file $ext`

    # Do things with $fname
done;

Dies erfordert keine vorherige Kenntnis der Dateierweiterung und funktioniert auch dann, wenn Sie einen Dateinamen haben, dessen Dateiname Punkte enthält (vor der Erweiterung). es erfordert das Programmbasename , aber dies ist Teil der GNU-Coreutils, daher sollte es mit jeder Distribution ausgeliefert werden.

Mike
quelle
1
Hervorragende Antwort! Entfernt die Erweiterung auf sehr saubere Weise, entfernt sie jedoch nicht. am Ende des Dateinamens.
Metrix
3
@metrix füge einfach das "." vor $ ext, dh: fname=`basename $file .$ext`
Carlos Troncoso
13

Pure Bash Way:

~$ x="/foo/bar/fizzbuzz.bar.quux.zoom"; 
~$ y=${x/\/*\//}; 
~$ echo ${y/.*/}; 
fizzbuzz

Diese Funktionalität wird auf man bash unter "Parametererweiterung" erläutert. Non-Bash-Wege gibt es zuhauf: awk, perl, sed und so weiter.

EDIT: Arbeiten mit Punkten in der Datei Suffixe und nicht das Suffix (Erweiterung) wissen müssen, aber nicht Arbeit mit Punkten im Namen selbst.

Vinko Vrsalovic
quelle
11

Die Funktionen basename und dirname sind genau das, wonach Sie suchen:

mystring=/foo/fizzbuzz.bar
echo basename: $(basename "${mystring}")
echo basename + remove .bar: $(basename "${mystring}" .bar)
echo dirname: $(dirname "${mystring}")

Hat Ausgabe:

basename: fizzbuzz.bar
basename + remove .bar: fizzbuzz
dirname: /foo
Jerub
quelle
1
Es wäre hilfreich, das Zitat hier zu korrigieren - führen Sie dies möglicherweise über shellcheck.net mit mystring=$1dem aktuellen konstanten Wert aus (wodurch mehrere Warnungen unterdrückt werden, wobei sichergestellt ist , dass keine Leerzeichen / Glob-Zeichen / usw. enthalten sind), und beheben Sie die Probleme findet?
Charles Duffy
Nun, ich habe einige entsprechende Änderungen vorgenommen, um Anführungszeichen in $ mystring zu unterstützen.
Meine Güte, das ist
Es wäre eine weitere Verbesserung, die Ergebnisse zu zitieren: echo "basename: $(basename "$mystring")"- Auf diese Weise, wenn mystring='/foo/*'Sie die *Dateien nach basename Abschluss nicht durch eine Liste von Dateien im aktuellen Verzeichnis ersetzen lassen .
Charles Duffy
6

Verwenden von basename davon , dass Sie die Dateierweiterung kennen, nicht wahr?

Und ich glaube, dass die verschiedenen Vorschläge für reguläre Ausdrücke nicht mit einem Dateinamen fertig werden, der mehr als ein "enthält".

Das Folgende scheint mit Doppelpunkten fertig zu werden. Oh, und Dateinamen, die selbst ein "/" enthalten (nur für Tritte)

Um Pascal zu paraphrasieren: "Entschuldigung, dieses Skript ist so lang. Ich hatte keine Zeit, es kürzer zu machen."


  #!/usr/bin/perl
  $fullname = $ARGV[0];
  ($path,$name) = $fullname =~ /^(.*[^\\]\/)*(.*)$/;
  ($basename,$extension) = $name =~ /^(.*)(\.[^.]*)$/;
  print $basename . "\n";
 
Andrew Edgecombe
quelle
1
Das ist schön und robust
Gaurav Jain
4

Neben die POSIX - konformen Syntax in verwendete diese Antwort ,

basename string [suffix]

wie in

basename /foo/fizzbuzz.bar .bar

GNUbasename unterstützt eine andere Syntax:

basename -s .bar /foo/fizzbuzz.bar

mit dem gleichen Ergebnis. Der Unterschied und Vorteil ist , dass -sbedeutet -a, die mehrere Argumente unterstützt:

$ basename -s .bar /foo/fizzbuzz.bar /baz/foobar.bar
fizzbuzz
foobar

Dies kann sogar -zdatensamensicher gemacht werden, indem die Ausgabe mit der Option durch NUL-Bytes getrennt wird , beispielsweise für diese Dateien, die Leerzeichen, Zeilenumbrüche und Glob-Zeichen enthalten (zitiert durch ls):

$ ls has*
'has'$'\n''newline.bar'  'has space.bar'  'has*.bar'

Einlesen in ein Array:

$ readarray -d $'\0' arr < <(basename -zs .bar has*)
$ declare -p arr
declare -a arr=([0]=$'has\nnewline' [1]="has space" [2]="has*")

readarray -derfordert Bash 4.4 oder neuer. Für ältere Versionen müssen wir eine Schleife ausführen:

while IFS= read -r -d '' fname; do arr+=("$fname"); done < <(basename -zs .bar has*)
Benjamin W.
quelle
Außerdem wird das angegebene Suffix in der Ausgabe entfernt, falls vorhanden (und ansonsten ignoriert).
Aksh1618
3
perl -pe 's/\..*$//;s{^.*/}{}'
Mopoke
quelle
3

Wenn Sie den in anderen Posts vorgeschlagenen Basisnamen nicht verwenden können, können Sie immer sed verwenden. Hier ist ein (hässliches) Beispiel. Es ist nicht das Beste, aber es funktioniert, indem die gewünschte Zeichenfolge extrahiert und die Eingabe durch die gewünschte Zeichenfolge ersetzt wird.

echo '/foo/fizzbuzz.bar' | sed 's|.*\/\([^\.]*\)\(\..*\)$|\1|g'

Welches erhalten Sie die Ausgabe

Fizzbuzz

Nymacro
quelle
Obwohl dies die Antwort auf die ursprüngliche Frage ist, ist dieser Befehl nützlich, wenn ich Pfadzeilen in einer Datei habe, um Basisnamen zu extrahieren und sie auf dem Bildschirm auszudrucken.
Sangcheol Choi
2

Hüten Sie sich vor der vorgeschlagenen Perl-Lösung: Sie entfernt alles nach dem ersten Punkt.

$ echo some.file.with.dots | perl -pe 's/\..*$//;s{^.*/}{}'
some

Wenn Sie es mit Perl machen wollen, funktioniert dies:

$ echo some.file.with.dots | perl -pe 's/(.*)\..*$/$1/;s{^.*/}{}'
some.file.with

Wenn Sie jedoch Bash verwenden, sind die Lösungen mit y=${x%.*}(oder basename "$x" .extwenn Sie die Erweiterung kennen) viel einfacher.

mivk
quelle
1

Der Basisname macht das, entfernt den Pfad. Es wird auch das Suffix entfernt, wenn es angegeben ist und mit dem Suffix der Datei übereinstimmt, aber Sie müssten das Suffix kennen, das Sie dem Befehl geben können. Andernfalls können Sie mv verwenden und herausfinden, wie der neue Name anders lauten soll.

Waynecolvin
quelle
1

Kombinieren Sie die am besten bewertete Antwort mit der am zweitbesten bewerteten Antwort, um den Dateinamen ohne den vollständigen Pfad zu erhalten:

$ x="/foo/fizzbuzz.bar.quux"
$ y=(`basename ${x%%.*}`)
$ echo $y
fizzbuzz
c.gutierrez
quelle
Warum verwenden Sie hier ein Array? Warum überhaupt einen Basisnamen verwenden?
Codeforester