Bash-Skriptfehler mit Zeichenfolgen mit Pfaden mit Leerzeichen und Platzhaltern

Ich habe Probleme, die Grundlagen der Bash-Skripterstellung zu verbessern. Folgendes habe ich bisher:

#!/bin/bash FILES="/home/john/my directory/*.txt" for f in "${FILES}" do echo "${f}" done 

Ich möchte nur alle .txt -Dateien auflisten in einer for -Schleife, damit ich Sachen damit machen kann. Aber das Leerzeichen in my directory und das Sternchen in *.txt spielt einfach nicht gut. Ich habe versucht, es mit und ohne doppelte Anführungszeichen, mit und ohne geschweifte Klammern auf Variablennamen zu verwenden, und kann immer noch nicht alle .txt -Dateien drucken.

Dies ist eine Sehr einfache Sache, aber ich habe immer noch Probleme, weil ich müde bin und nicht klar denken kann.

Was mache ich falsch?

Ich konnte mich erfolgreich bewerben das obige Skript, wenn meine DATEIEN kein Leerzeichen oder Sternchen haben … Ich musste mit oder ohne doppelte Anführungszeichen und geschweifte Klammern experimentieren, damit es funktioniert. Aber in dem Moment, in dem ich beide Leerzeichen und ein Sternchen habe, wird alles durcheinander gebracht.

Antwort

In Anführungszeichen die * wird nicht zu einer Liste von Dateien erweitert. Um einen solchen Platzhalter erfolgreich zu verwenden, muss er außerhalb von Anführungszeichen stehen.

Selbst wenn der Platzhalter erweitert würde, würde der Ausdruck "${FILES}" zu einer einzelnen Zeichenfolge führen, nicht eine Liste von Dateien.

Ein Ansatz, der funktionieren würde, wäre:

#!/bin/bash DIR="/home/john/my directory/" for f in "$DIR"/*.txt do echo "${f}" done 

Oben Dateinamen mit Leerzeichen oder anderen schwierigen Zeichen werden korrekt behandelt.

Ein fortgeschrittener Ansatz könnte Bash-Arrays verwenden:

#!/bin/bash FILES=("/home/john/my directory/"*.txt) for f in "${FILES[@]}" do echo "${f}" done 

In diesem Fall FILES ist ein Array von Dateinamen. Die Parens, die die Definition umgeben, machen sie zu einem Array. Beachten Sie, dass * außerhalb von Anführungszeichen steht. Das Konstrukt "${FILES[@]}" ist ein Sonderfall: Es wird zu einer Liste von Zeichenfolgen erweitert, wobei jede Zeichenfolge einer der Dateinamen ist. Dateinamen mit Leerzeichen oder anderen schwierigen Zeichen werden korrekt behandelt.

Kommentare

  • großartig, dass es funktioniert hat
  • Es ‚ Es ist erwähnenswert, dass Sie, wenn Sie ‚ Pfade wie diesen durch Funktionen übergeben, sicherstellen müssen, dass Sie die Variable selbst in Anführungszeichen setzen anstatt es als Teil einer größeren Zeichenfolge zu verketten: for f in "$DIR"/*.txt = fine for f in "$DIR/*.txt" = bricht

Antwort

Während die Verwendung von Arrays, wie von John1024 gezeigt, viel sinnvoller ist, können Sie hier auch den Operator split + glob verwenden (verlassen) eine skalare Variable ohne Anführungszeichen).

Da Sie nur den Glob-Teil dieses Operators möchten, müssen Sie den split -Teil deaktivieren:

#! /bin/sh - # that also works in any sh, so you don"t even need to have or use bash file_pattern="/home/john/my directory/*.txt" # all uppercase variables should be reserved for environment variables IFS="" # disable splitting for f in $file_pattern # here we"re not quoting the variable so # we"re invoking the split+glob operator. do printf "%s\n" "$f" # avoid the non-reliable, non-portable "echo" done 

Antwort

Sie können nur die Platzhalterzeichen außerhalb von Anführungszeichen lassen.
So etwas wie:
für a in „Dateien mit Leerzeichen“ * „. txt „
tun
verarbeiten
erledigt
Wenn die Platzhalter selbst zu Leerzeichen erweitert werden, müssen Sie keinen zeilenweisen Ansatz verwenden, z. B. ls -l verwenden, um die Liste der Dateien und zu generieren Verwenden Sie bash read, um jede Datei abzurufen.

Antwort

Einzeilige Lösung (zur Ausführung im Terminal):
/usr/bin/find "./" -not -type d -maxdepth 1 -iname "*.txt" -print0 | while IFS= read -r -d $"\0" f ; do { echo "${f}"; }; done; unset f;

Ändern Sie für den Fall Ihres / OP die "./" in

So verwenden Sie es in einer Skriptdatei:

 #!/bin/bash /usr/bin/find "./" -not -type d -maxdepth 1 -iname "*.txt" -print0 | while IFS= read -r -d $"\0" f ; do { echo "${f}"; # your other commands/codes, etc }; done; unset f;  

Die oben genannten Funktionen können auch auf diese (empfohlene) Weise erreicht werden:

 #!/bin/bash while IFS= read -r -d $"\0" f ; do { echo "${f}"; # your other commands/codes, etc }; done < <(/usr/bin/find "./" -not -type d -maxdepth 1 -iname "*.txt" -print0); unset f;  

Kurz / Kurz BESCHREIBUNG:

"./": Dies ist das aktuelle Verzeichnis. Geben Sie einen Verzeichnispfad an.
-not -type d: Hier konfiguriert die -not ihn zum Überspringen des Der nächste erwähnte Typ, & Der nächste erwähnte -type ist d = Verzeichnisse, daher wird er übersprungen Verzeichnisse. Verwenden Sie f anstelle von d, um Dateien zu überspringen. Verwenden Sie l anstelle von d, um Symlink-Dateien zu überspringen.
-maxdepth 1: Konfiguriert es so, dass nur Dateien auf der aktuellen Verzeichnisebene (auch bekannt als: one) gefunden werden. Um eine Datei in jeder & ersten Unterverzeichnisebene zu finden, setzen Sie maxdepth auf 2. Wenn -maxdepth nicht verwendet wird, wird rekursiv gesucht (innerhalb von Unterverzeichnissen) usw.
-iname "*.jpg": Hier konfiguriert die -iname sie so, dass sie Dateien findet und ignoriert (oben / unten) -case in Dateiname / Erweiterung. Die -name ignoriert Groß- und Kleinschreibung nicht. Die -lname findet Symlinks. usw.
-print0: Gibt den Pfadnamen der aktuellen Datei in die Standardausgabe aus, gefolgt von einem ASCII-NUL-Zeichen (Zeichencode 0) später anhand der read in while erkennen.
IFS=: Hier wird es verwendet, wenn ein Dateiname mit einem Leerzeichen endet. Wir verwenden NUL / "" / \0 mit IFS, um jeden gefundenen Dateinamen zu ermitteln. Da “ find “ so konfiguriert ist, dass sie mit \0 wird von -print0 erzeugt.
read -r -d $"\0" fileName: Die $"\0" ist "" / NUL. Die -r wurde verwendet, wenn ein Dateiname einen Backslash aufweist.
read [-ers] [-a aname] [-d delim] [-i text] [-n nchars] [-N nchars] [-p Eingabeaufforderung] [-t timeout] [-u fd] [name. ..] […]
-r Backslash fungiert nicht als Escape-Charakter. Der Backslash wird als Teil der Zeile betrachtet. Insbesondere darf ein Backslash-Newline-Paar nicht als Zeilenfortsetzung verwendet werden.
done < <(...): Prozesssubstitution, die hier zum Senden von / verwendet wird Pipe-Ausgabe von “ find “ in die “ read “ von “ während “ -Schleife. Weitere Informationen: https://www.gnu.org/software/bash/manual/html_node/Process-Substitution.html, https://wiki.bash-hackers.org/syntax/expansion/proc_subst, https://tldp.org/LDP/abs/html/abs-guide.html#PROCESS-SUB, https://tldp.org/LDP/abs/html/abs-guide.html#PSUBP


In einer anderen Antwort von @ John1024 hat er eine großartige Bash-basierte Lösung gezeigt, die nicht die find „, ein externes Dienstprogramm.
“ find “ ist sehr effektiv & schnell, ich bevorzuge es, wenn zu viele Dateien verarbeitet werden müssen.

In der Lösung von @ John1024 wird die Datei ausgedruckt Matching-Regelzeile, wenn sich keine Datei im Verzeichnis befindet. Unter der folgenden Zeile [ ! -e "${f}" ]... wird dies übersprungen.
Hier ist eine einzeilige Lösung, die direkt im Terminal verwendet werden kann:
DIR="/home/john/my directory/" ; for f in "$DIR"*.txt ; do { [ ! -e "${f}" ] && continue; echo "${f}"; }; done; unset DIR;

Hier ist ein Skript:

 #!/bin/bash DIR="/home/john/my directory/"; for f in "$DIR"*.txt ; do { [ ! -e "${f}" ] && continue; echo "${f}"; # your codes/commands, etc }; done; unset DIR;  

Hinweis: Wenn das Verzeichnis in DIR am Ende einen "/" Schrägstrich (Verzeichnisindikator) enthält, dann in Übereinstimmungsregel Auch hier ist die Verwendung von "/" nicht erforderlich.
Oder machen Sie das Gegenteil: Verwenden Sie in DIR nicht die "/" am Ende Verwenden Sie es daher in der Matching-Regel "$DIR"/*.txt


Diese zusätzliche Überprüfung mithilfe der [ ! -e "${f}" ]... Code kann vermieden werden, wenn unter Shell-Option (auch bekannt als: “ shopt „) wird verwendet oder aktiviert:
shopt -s nullglob

Wenn ein Shopt-Status durch ein Skript geändert wurde, führt dies in einem anderen Bash-basierten Skriptprogramm zu unerwarteten / unerwarteten Problemen.

Um über alle verwendeten Skripte hinweg ein konsistentes Verhalten zu erzielen Der Status von bash, bash-shell-option sollte in Ihrem Skript aufgezeichnet / gespeichert werden. Sobald Sie Ihre primären Funktionen in Ihrem Skript verwendet haben, sollte diese Shell-Option auf die vorherigen Einstellungen zurückgesetzt werden.

Wir verwenden Backtick (auch bekannt als: Grabakzent, auch bekannt als Backquote) `...` Befehlssubstitution (für interne Bash-Befehlscodes usw.), um keine neue Sub-Shell zu erzeugen, Behalten Sie die wörtliche Bedeutung von Backslash bei, um eine breitere Unterstützung (auch bekannt als: Portabilität) usw. zu erhalten. Da Backtick-basierte interne Bash-Befehle usw. häufig in derselben Shell wie das Skript usw. ausgeführt werden können, ist sie etwas schneller & besser und auch besser für den Zweck, den wir hier behandeln. Wenn Sie die Befehlsersetzung $(...) bevorzugen, verwenden Sie diese. Jeder hat die Freiheit, & zu wählen, was er bevorzugt, vermeidet usw. Weitere Informationen : hier .

Das obige Skript wird also erneut angezeigt: & diesmal mit den vorherigen Einstellungen eines Shopt wiederhergestellt, bevor das Skript beendet wird:

 #!/bin/bash DIR="/home/john/my directory/"; ub="/usr/bin"; # shopt = shell-option(s). # Setting-up "previous-nullglob" state to "enabled"/"on"/"1": p_nullglob=1; # The "shopt -s" command output shows list of enabled shopt list, so if # nullglob is NOT set to ON/enabled, then setting "previous_nullglob" as 0 [ "`shopt -s | ${ub}/grep nullglob`" == "" ] && p_nullglob=0; # Enabling shell-options "nullglob": shopt -s nullglob; # previous code, but without the extra checking [ ! -e "${f}" ]... line: for f in "$DIR"*.txt ; do { echo "${f}"; # your codes/commands, etc }; done; # As we have utilized enabled nullglob shopt, its now in enabled state, # so if previously it was disabled only-then we will disable it: [ "$p_nullglob" -eq "0" ] && shopt -u nullglob; unset DIR; unset p_nullglob ub;  

Ausgabe von shopt -p shoptName (zum Beispiel: shopt -p dotglob) kann entweder
sein: shopt -u shoptName (die u ist unset / disabled / off / 0)
oder dies: shopt -s shoptName ( Die s ist set / enabled / on / 1)
Die Position des Buchstabens "s" oder "u" steht immer bei 7 (weil in bash a Die Buchstabenposition der Zeichenfolge beginnt bei 0, dh der erste Buchstabe einer Zeichenfolge befindet sich an der Position 0)
We kann diese "u" oder und speichern Sie es in einer Variablen, damit wir damit den vorherigen Status wiederherstellen können.
Und wenn wir diese (oben erwähnte) Methode anwenden, um den shopt-Status zu speichern / wiederherzustellen, können wir dies tun Vermeiden Sie die Verwendung des externen Tools "grep".

Zum Anzeigen der Datei "txt", die mit ".", dh um versteckte "txt" -Datei anzuzeigen, müssen Sie "dotglob" shopt aktivieren.

Diesmal sind also "dotglob" enthalten. & aktiviert, um versteckte "txt" -Dateien anzuzeigen:

 #!/bin/bash DIR="/home/john/my directory/"; p_nullglob="u"; pSS="`shopt -p nullglob`"; [ "${pSS:7:1}" == "s" ] && p_nullglob="s"; p_dotglob="u"; pSS="`shopt -p dotglob`"; [ "${pSS:7:1}" == "s" ] && p_dotglob="s"; shopt -s nullglob dotglob; for f in "$DIR"*.txt ; do { echo "${f}"; # your codes/commands, etc }; done; [ "$p_nullglob" == "u" ] && shopt -u nullglob; [ "$p_dotglob" == "u" ] && shopt -u dotglob; unset DIR; unset p_nullglob p_dotglob pSS;  

Es gibt einfachere Möglichkeiten, shopt zu speichern / wiederherzustellen Option / Wert.
Isaac hat hier gepostet, wie man Env speichert + wiederherstellt / Shopt-Variable / Optionsstatus / -wert.

Speichern des Shopt-Status von “ nullglob „:

... # your primary-function codes/commands, etc lines
Wiederherstellen des vorherigen Shopt-Status von “ nullglob „, bevor Sie das Skript beenden:
eval "$p_nullglob" ;

Mehrere Shopt-Zustände können auf folgende Weise gespeichert werden:
p_multipleShopt="`shopt -p nullglob dotglob`";
und der Wiederherstellungsprozess ist der gleiche wie zuvor:
eval "$p_multipleShopt" ;

Speichern Sie ALLE shopt-Zustände wie folgt:
p_allShopt="`shopt -p`";
und der Wiederherstellungsprozess ist der gleiche wie zuvor:
eval "$p_allShopt" ;

Hier ist eine weitere Bash-basierte Lösung:

 #!/bin/bash DIR="/home/john/my directory/"; p_allShopt="`shopt -p`"; shopt -s nullglob dotglob; for f in "$DIR"*.txt ; do { echo "${f}"; # your codes/commands, etc }; done; eval "$p_allShopt" ; unset DIR p_allShopt;  

Die Verwendung von eval ist oben sicher, da die Variable "$p_allShopt" keine von einem Benutzer bereitgestellten Daten oder Daten enthält, die nicht- bereinigt, dass var die Ausgabe des internen Bash-Befehls shopt hält.
Wenn Sie eval dennoch vermeiden möchten, verwenden Sie dies unten Lösung:

 #!/bin/bash DIR="/home/john/my directory/"; p_allShopt="`shopt -p`"; shopt -s nullglob dotglob; for f in "$DIR"*.txt ; do { echo "${f}"; # your codes/commands, etc }; done; while IFS= read -a oneLine ; do { ${oneLine} ; }; done < <(echo "$p_allShopt") ; unset DIR p_allShopt oneLine;  

Nur wenige (andere) bemerkenswerte & verwandte SHOPT , die nützlich sein können, sind:

  • nocaseglob: Wenn gesetzt, stimmt Bash mit Dateinamen in a überein Bei der Dateinamenerweiterung wird die Groß- und Kleinschreibung nicht berücksichtigt.
  • nocasematch: Wenn diese Option festgelegt ist, werden bei der Ausführung von case oder [[ bedingte Befehle, beim Durchführen von Wortersetzungsworterweiterungen oder beim Filtern möglicher Vervollständigungen als Teil der programmierbaren Vervollständigung.
  • dotglob: Wenn gesetzt, enthält Bash Dateinamen, die mit einem ‘.’ in den Ergebnissen der Dateinamenerweiterung. Die Dateinamen ‘.’ und ‘..’ müssen immer explizit übereinstimmen, auch wenn dotglob gesetzt ist.
  • nullglob: Wenn gesetzt Mit Bash können Dateinamenmuster, die keinen Dateien entsprechen, zu einer Nullzeichenfolge und nicht zu sich selbst erweitert werden.
  • extglob: Wenn diese Option aktiviert ist, werden die oben beschriebenen erweiterten Mustervergleichsfunktionen verwendet (siehe Pattern Matching ) sind aktiviert.
  • globstar: Wenn festgelegt, stimmt das in einem Dateinamenerweiterungskontext verwendete Muster ‘**’ mit allen Dateien und überein null oder mehr Verzeichnisse und Unterverzeichnisse. Wenn dem Muster ein ‘/’ folgt, stimmen nur Verzeichnisse und Unterverzeichnisse überein.

Antwort

Wenn Sie eine Reihe von Dateien verarbeiten möchten, denken Sie, dass an deren Namen möglicherweise Leerzeichen oder ein anderer Scape-Code vorhanden ist. Bevor Sie also mit dem Vorgang beginnen, z. B. for loop oder find command setzen Sie die IFS bash env variable auf:

IFS=$(echo -en "\n\b") 

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.