Diese Lösungen, auf die Sie verweisen, sind wirklich gut. Bei einigen Antworten kann es an Erklärungen mangeln, also lass uns das klären, vielleicht noch etwas hinzufügen.
Diese Zeile von dir
for file in *.txt
zeigt an, dass die Erweiterung im Voraus bekannt ist (Hinweis: In POSIX-kompatiblen Umgebungen wird die Groß- und Kleinschreibung beachtet, es *.txt
stimmt nicht überein FOO.TXT
). In diesem Fall
basename -s .txt "$file"
sollte den Namen ohne die Erweiterung zurückgeben ( basename
entfernt auch den Verzeichnispfad: /directory/path/filename
→ filename
; in Ihrem Fall spielt es keine Rolle, da er $file
keinen solchen Pfad enthält). Um das Werkzeug in Ihrem Code zu verwenden, müssen Sie die Befehlsersetzung, die in der Regel wie folgt aussieht: $(some_command)
. Die Befehlsersetzung übernimmt die Ausgabe von some_command
, behandelt sie als Zeichenfolge und platziert sie, wo sie $(…)
ist. Ihre besondere Weiterleitung wird sein
… > "./$(basename -s .txt "$file")_sorted.txt" # ^^^^^^^^^^^^^^^^^^^^^^^^^^^ the output of basename will replace this
Verschachtelte Anführungszeichen sind hier in Ordnung, da Bash klug genug ist, um zu wissen, dass die Anführungszeichen in einem $(…)
Paar gepaart sind.
Dies kann verbessert werden. Hinweis basename
ist eine separate ausführbare Datei, keine eingebaute Shell (in Bash-Ausführung type basename
, Vergleich mit type cd
). Das Anlegen zusätzlicher Prozesse ist kostspielig und erfordert Ressourcen und Zeit. Das Ablegen in einer Schleife funktioniert normalerweise schlecht. Verwenden Sie daher das, was Ihnen die Shell bietet, um zusätzliche Prozesse zu vermeiden. In diesem Fall lautet die Lösung:
… > "./$_sorted.txt"
Die Syntax wird unten für einen allgemeineren Fall erläutert.
Falls Sie die Erweiterung nicht kennen:
… > "./$_sorted.$"
Die Syntax erklärt:
$
-$file
, aber der kürzeste passende String*.
wird von vorne entfernt;$
-$file
, aber der längste passende String*.
wird von vorne entfernt; Verwenden Sie es, um nur eine Erweiterung zu erhalten;$
-$file
, aber der kürzeste passende String.*
wird am Ende entfernt; Verwenden Sie es, um alles außer Erweiterung zu erhalten;$
-$file
, aber die längste Zeichenfolge.*
wird vom Ende entfernt;
Der Musterabgleich ist glob-artig und nicht regulär. Das bedeutet, *
ein Platzhalter für null oder mehr Zeichen, ?
ein Platzhalter für genau ein Zeichen ( ?
in Ihrem Fall ist dies jedoch nicht erforderlich ). Wenn Sie den gleichen Pattern-Matching-Mechanismus aufrufen ls *.txt
oder for file in *.txt;
verwenden. Ein Muster ohne Platzhalter ist zulässig. Wir haben bereits verwendet, $
wo .txt
das Muster ist.
Beispiel:
$ file=name.name2.name3.ext $ echo "$" name2.name3.ext $ echo "$" ext $ echo "$" name.name2.name3 $ echo "$" name
Aber Vorsicht:
$ file=extensionless $ echo "$" extensionless $ echo "$" extensionless $ echo "$" extensionless $ echo "$" extensionless
Aus diesem Grunde ist die folgende contraption könnte nützlich sein (aber es ist nicht, Erklärung unten):
$}
Es identifiziert alles außer extension ( $
) und entfernt es dann aus dem gesamten String. Die Ergebnisse sind wie folgt:
$ file=name.name2.name3.ext $ echo "$}" .ext $ file=extensionless $ echo "$}" $ # empty output above
Beachten Sie, dass .
diesmal enthalten ist. Sie erhalten möglicherweise unerwartete Ergebnisse, wenn Sie ein $file
Literal *
oder enthalten ?
. Aber Windows (wo Erweiterungen eine Rolle spielen) lässt diese Zeichen in Dateinamen sowieso nicht zu, so dass es Sie vielleicht nicht interessiert. Jedoch […]
oder {…}
, falls vorhanden, kann ihr eigenes Muster Anpassungsschema auslösen und die Lösung brechen!
Ihre "verbesserte" Weiterleitung wäre:
… > "./$_sorted$}"
Es sollte Dateinamen mit oder ohne Erweiterung unterstützen, allerdings nicht mit eckigen oder geschweiften Klammern. Schade eigentlich. Um dies zu beheben, müssen Sie die innere Variable doppelt zitieren.
Wirklich verbesserte Weiterleitung:
… > "./$_sorted$"}"
Doppelte Anführungszeichen $
wirken nicht als Muster! Bash ist intelligent genug, um innere und äußere Anführungszeichen voneinander zu unterscheiden, da die inneren in die äußere ${…}
Syntax eingebettet sind . Ich denke, das ist der richtige Weg .
Eine weitere (unvollkommene) Lösung, analysieren wir sie aus pädagogischen Gründen:
$
Es ersetzt das erste .
mit _sorted.
. Es wird gut funktionieren, wenn Sie höchstens einen Punkt haben $file
. Es gibt eine ähnliche Syntax $
, die alle Punkte ersetzt. Soweit ich weiß, gibt es keine Variante, die nur den letzten Punkt ersetzt.
Immer noch die erste Lösung für Dateien, die .
robust aussehen. Die Lösung für erweiterungs $file
ist trivial: $_sorted
. Jetzt brauchen wir nur noch eine Möglichkeit, die beiden Fälle voneinander zu unterscheiden. Hier ist es:
[[ "$file" == *?.* ]]
Exit-Status 0 (wahr) wird nur dann zurückgegeben, wenn der Inhalt der $file
Variablen dem Muster auf der rechten Seite entspricht. Das Muster sagt "es gibt nach mindestens einem Zeichen einen Punkt" oder "Es gibt einen Punkt, der nicht am Anfang steht". Es geht darum, versteckte Linux-Dateien (zB .bashrc
) als erweiterungslos zu behandeln, es sei denn, es gibt irgendwo einen anderen Punkt.
Beachten Sie, dass wir [[
hier nicht brauchen [
. Ersteres ist leistungsfähiger, aber leider nicht portabel ; Letzteres ist tragbar, aber für uns zu begrenzt.
Die Logik geht jetzt so:
[[ "$file" == *?.* ]] && file1="./$_sorted.$" || file1="$_sorted"
Danach, $file1
enthält den gewünschten Namen, so sollten Sie Ihre Umleitung sein
… > "./$file1"
Und das gesamte Code-Snippet ( *.txt
ersetzt durch, *
um anzuzeigen, dass wir mit einer Erweiterung oder keiner Erweiterung arbeiten):
for file in *; do printf 'Processing %s\n' "$file" [[ "$file" == *?.* ]] && file1="./$_sorted.$" || file1="$_sorted" LC_ALL=C sort -u "$file" > "./$file1" done
Dies würde auch versuchen, Verzeichnisse (falls vorhanden) zu verarbeiten. Sie wissen bereits, was Sie tun müssen, um das Problem zu beheben.