![Kürzeste Möglichkeit, die letzten 3 Zeichen des Basisdateinamens (ohne Suffix) zu extrahieren](https://rvso.com/image/38704/K%C3%BCrzeste%20M%C3%B6glichkeit%2C%20die%20letzten%203%20Zeichen%20des%20Basisdateinamens%20(ohne%20Suffix)%20zu%20extrahieren.png)
Ich versuche, eine Variable in einem SH-Skript auf die letzten 3 Zeichen des Basisnamens einer Datei zu setzen (mit Basisnamen meine ich ohne den PfadUndohne Suffix). Das ist mir gelungen, aber ich frage mich aus reiner Neugier, ob es einen kürzeren, einzelnen Befehl gibt, den ich verwenden kann. Ursprünglich hatte ich einen Einzeiler mit awk
, aber der war ziemlich lang. Derzeit habe ich dieses zweizeilige Skript (vorausgesetzt, ein vollständiger Dateiname befindet sich in $1
):
filebase=`basename "$1"`
lastpart=`echo -n ${filebase%.*} | tail -c3`
So zum Beispiel"/Pfad/zu/einerDatei.txt"endet mit"il"In $lastpart
.
Kann ich und das Bit zum Entfernen des Suffixes irgendwie in einem einzigen Befehl kombinieren basename
, und gibt es eine Möglichkeit, es an zu senden tail
(oder etwas anderes, das ich verwenden kann), ohne eine Pipe zu verwenden? Das Suffix ist unbekannt, daher kann ich es nicht als Parameter für verwenden basename
.
Das Hauptziel ist eigentlich nicht so sehr, so kurz wie möglich zu sein, sondern so lesbar wie möglich auf einen Blick. Der eigentliche Kontext von all dem istdiese Frage auf Superuser, wo ich versuche, eine einigermaßen einfache Antwort zu finden.
Antwort1
var=123456
echo "${var#"${var%???}"}"
###OUTPUT###
456
Dadurch werden zuerst die letzten drei Zeichen von entfernt und $var
dann aus $var
den Ergebnissen dieser Entfernung - was die letzten drei Zeichen von zurückgibt $var
. Hier sind einige Beispiele, die genauer zeigen sollen, wie Sie so etwas tun könnten:
touch file.txt
path=${PWD}/file.txt
echo "$path"
/tmp/file.txt
base=${path##*/}
exten=${base#"${base%???}"}
base=${base%."$exten"}
{
echo "$base"
echo "$exten"
echo "${base}.${exten}"
echo "$path"
}
file
txt
file.txt
/tmp/file.txt
Sie müssen das nicht alles auf so viele Befehle verteilen. Sie können es komprimieren:
{
base=${path##*/} exten=
printf %s\\n "${base%.*}" "${exten:=${base#"${base%???}"}}" "$base" "$path"
echo "$exten"
}
file
txt
file.txt
/tmp/file.txt
txt
Die Kombination $IFS
mit set
Ting-Shell-Parametern kann auch ein sehr effektives Mittel zum Parsen und Durchforsten von Shell-Variablen sein:
(IFS=. ; set -f; set -- ${path##*/}; printf %s "${1#"${1%???}"}")
Damit erhalten Sie nur die drei Zeichen unmittelbar vor dem ersten Punkt nach dem letzten /
in . Wenn Sie nur die ersten drei Zeichen unmittelbar vor dem letzten in $path
abrufen möchten.
$path
(zum Beispiel, wenn die Möglichkeit besteht, dass .
der Dateiname mehr als eins enthält):
(IFS=.; set -f; set -- ${path##*/}; ${3+shift $(($#-2))}; printf %s "${1#"${1%???}"}")
In beiden Fällen können Sie Folgendes tun:
newvar=$(IFS...)
Und...
(IFS...;printf %s "$2")
...druckt das Folgende aus:.
Wenn es Ihnen nichts ausmacht, ein externes Programm zu verwenden, können Sie Folgendes tun:
printf %s "${path##*/}" | sed 's/.*\(...\)\..*/\1/'
Wenn die Möglichkeit besteht, dass \n
der Dateiname ein Ewline-Zeichen enthält(gilt nicht für die nativen Shell-Lösungen – die kommen sowieso alle damit klar):
printf %s "${path##*/}" | sed 'H;$!d;g;s/.*\(...\)\..*/\1/'
Antwort2
Das ist ein typischer Job für expr
:
$ file=/path/to/abcdef.txt
$ expr "/$file" : '.*\([^/.]\{3\}\)\.[^/.]*$'
def
Wenn Sie wissen, dass Ihre Dateinamen das erwartete Format haben (enthält genau einen Punkt und mindestens 3 Zeichen vor dem Punkt), kann dies wie folgt vereinfacht werden:
expr "/$file" : '.*\(.\{3\}\)\.'
Beachten Sie, dass der Beendigungsstatus ungleich Null ist, wenn keine Übereinstimmung vorliegt, aber auch, wenn der übereinstimmende Teil eine Zahl ist, die sich in 0 auflöst. (wie für a000.txt
oder a-00.txt
)
Mit zsh
:
file=/path/to/abcdef.txt
lastpart=${${file:t:r}[-3,-1]}
( :t
fürSchwanz(Basisname) :r
fürausruhen(mit entfernter Erweiterung)).
Antwort3
Wenn Sie Folgendes verwenden können perl
:
lastpart=$(
perl -e 'print substr((split(/\.[^.]*$/,shift))[0], -3, 3)
' -- "$(basename -- "$1")"
)
Antwort4
Wenn Perl verfügbar ist, finde ich, dass es lesbarer sein kann als andere Lösungen, insbesondere weil seine Regex-Sprache ausdrucksstärker ist und über den /x
Modifikator verfügt, der das Schreiben klarerer Regex-Ausdrücke ermöglicht:
perl -e 'print $1 if shift =~ m{ ( [^/]{3} ) [.] [^./]* \z }x' -- "$file"
Dies gibt nichts aus, wenn es keine solche Übereinstimmung gibt (wenn der Basisname keine Erweiterung hat oder wenn die Wurzel vor der Erweiterung zu kurz ist). Je nach Ihren Anforderungen können Sie den regulären Ausdruck anpassen. Dieser reguläre Ausdruck erzwingt die Einschränkungen:
- Es entspricht den 3 Zeichen vor der letzten Erweiterung (dem Teil nach und einschließlich dem letzten Punkt). Diese 3 Zeichen können einen Punkt enthalten.
- Die Erweiterung kann (bis auf den Punkt) leer sein.
- Der übereinstimmende Teil und die Erweiterung müssen Teil des Basisnamens sein (der Teil nach dem letzten Schrägstrich).
Die Verwendung in einer Befehlsersetzung hat die üblichen Probleme mit dem Entfernen zu vieler nachfolgender Zeilenumbrüche zur Folge, ein Problem, das auch Stéphanes Antwort betrifft. Es kann in beiden Fällen behandelt werden, ist hier aber etwas einfacher:
lastpart=$(
perl -e 'print "$1x" if shift =~ m{ ( [^/]{3} ) [.] [^./]* \z }x' -- "$file"
)
lastpart=${lastpart%x} # allow for possible trailing newline