Kürzeste Möglichkeit, die letzten 3 Zeichen des Basisdateinamens (ohne Suffix) zu extrahieren

Kürzeste Möglichkeit, die letzten 3 Zeichen des Basisdateinamens (ohne Suffix) zu extrahieren

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 $vardann aus $varden 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 $IFSmit setTing-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 $pathabrufen 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 \nder 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.txtoder a-00.txt)

Mit zsh:

file=/path/to/abcdef.txt
lastpart=${${file:t:r}[-3,-1]}

( :tfürSchwanz(Basisname) :rfü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 /xModifikator 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:

  1. 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.
  2. Die Erweiterung kann (bis auf den Punkt) leer sein.
  3. 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

verwandte Informationen