OSX Bash for Loop - Probleme mit Leerzeichen in Ordnernamen?

8

Ich bin etwas verwirrt darüber, wie Leerzeichen in Pfadnamen behandelt werden sollen, wenn sie in einer for-Schleife zurückgegeben werden.

Begründung: Ich bereinige die Berechtigungen für Ordner und Dateien, die ich von Windows kopiere. Die meisten Dateien haben -rwx------oder -rwxr-xr-xBerechtigungen, daher mache ich gerne " chmod -x *" und dann " chmod u+x <folders>", also versuche ich Folgendes:

$ alias getdirs='find . -maxdepth 1 -mindepth 1 -type d | cut -c 3-'
$ for i in $(getdirs); do chmod u+x $i; done

Das funktioniert gut, solange die Verzeichnisse kein Leerzeichen im Namen haben.

Ich habe verschiedene Permutationen versucht chmod u+x "$i", chmod u+x '$i'und ähnliches , das Verhalten zu bekommen ich wollte, aber ohne Erfolg.

Wie kann ich meinen Bash-Code verbessern, der mit Ordnernamen funktioniert, die Leerzeichen enthalten?

Der Zweck davon ist, das "exec" -Bit aus einfachen Dateien (daher der chmod -x *Teil) entfernen zu können, es dann aber in den Verzeichnissen wiederherzustellen, um den Zugriff darauf zu ermöglichen ( chmod u+x <dirname>). Aufgrund der bisherigen Kommentare und Antworten denke ich, dass es wahrscheinlich einfacher sein wird, mit der richtigen Beschwörung "Finden" umzugehen

JJarava
quelle
Warum verwenden Sie den Schnitt - könnten Sie nicht einfach -exec verwenden?
user151019
2
Jeder für sich, das Aneinanderreihen einer Menge von Befehlen mit Pipes kann eine gute Möglichkeit sein, eine Art codebasierten Kommentar zu logischen Schritten bereitzustellen, während die meisten Dinge innerhalb von Befehlen wie findund sedund awkusw. auf wenige Schritte reduziert werden können, viele weniger erfahrene Shell-Bastler finden die Befehlsketten im Flowchart-Stil besser lesbar und verständlicher.
stuffe
@ Mark: Ich möchte die ./ am Anfang der Ausgabe loswerden, um "schöne" Ausgabe zu erhalten, das ist alles
JJarava
@stuffe - aber dann musst du dich mit diesem String-Problem befassen, damit ich beide gleich komplex finde. - Mein Vorschlag ist, die Shell aufzugeben und eine vollständigere, nicht textbasierte Sprache zu verwenden - z. B. Python, Perl, Tcl usw.
user151019
@Mark Eigentlich ist das "Plan b". Ich bin einigermaßen vertraut mit Python (es ist meine bevorzugte Skriptsprache für die Entwicklung von Dienstprogrammen) und seit ich auf den Mac umgestiegen bin, habe ich einige weitere entwickelt, aber ich habe versucht, dies "auf Unix-Art" zu tun. Von den anderen Antworten glaube ich, dass "der Weg zu gehen" (anders als ein ausgewachsenes Python-Skript) wahrscheinlich darin besteht, "finden" zu nutzen. Werde es untersuchen und
zurückmelden

Antworten:

10

Diese Art von Dingen kann in allen Unix-Shells schwierig sein, da der Speicherplatz als Trennzeichen fungiert. Das Ausführen von Aliasnamen als Teil von Shell-Skripten macht die Dinge nur noch interessanter. Ich würde wahrscheinlich zwei Suchdurchläufe ausführen, um zuerst die Verzeichnisse in der richtigen Reihenfolge und dann die Dateien festzulegen:

find . -maxdepth 1 -mindepth 1 -type d -exec chmod u+x '{}' \;
find . -maxdepth 1 -mindepth 1 -type f -exec chmod u-x '{}' \;
nohillside
quelle
3
Und um auf die anfängliche Notwendigkeit zu antworten:find . -maxdepth 1 -mindepth 1 -type f -exec chmod u-x '{}' \;
Dan
7

Im Allgemeinen besteht die "richtige" Methode zum Analysieren der Ausgabe von find in einer Bash-Schleife darin, eine while readSchleife anstelle einer forSchleife zu verwenden. In Bash für Schleifen, die standardmäßig mit einem beliebigen Leerzeichen (Leerzeichen, Tabulator, Zeilenumbruch) geteilt werden - dies kann geändert werden, ist jedoch einfacher und (meiner Meinung nach) sauberer zu verwenden read, da standardmäßig jeweils eine Zeile gelesen wird.

find . -maxdepth 1 -mindepth 1 -type d | while read i; do chmod u+x "$i"; done

Beachten Sie, dass ich das "$i"dort zitiert habe - das ist genauso wichtig, weil das Zitieren von Variablen verhindert, dass die Shell ihren Inhalt aufteilt (es ist das gleiche Problem, das es forgibt, aber am anderen Ende). Beachten Sie auch, dass Sie keine einfachen Anführungszeichen verwenden können: '$i'Gibt ein Literal $ianstelle des Inhalts der Variablen zurück.

Dies wird weiterhin in Verzeichnissen mit Zeilenumbrüchen in ihren Namen unterbrochen. Es gibt eine -print0Problemumgehung für Funds, aber ich habe bisher nur Zeilenumbrüche in Dateinamen gesehen, die speziell zum Testen von Skripten erstellt wurden. Ich weiß nicht, ob dies mit der Version von Bash in OSX funktioniert (aus Gregs Wiki ):

find . -maxdepth 1 -mindepth 1 -type d -print0 | while IFS= read -r -d '' i; do chmod u+x "$i"; done

In diesem Fall ist es jedoch einfacher, Globs zu verwenden: Ein Glob, der mit a endet, /wird nur auf Verzeichnisse erweitert, sodass Sie dies einfach tun können

chmod u+x */

(Dies funktioniert perfekt mit Leerzeichen, Zeilenumbrüchen usw.). Allgemeiner gesagt, um alle Verzeichnisse zu durchlaufen:

for f in */; do stuff with "$f"; done

Leider gibt es in keiner Bash-Version die Möglichkeit, Dateien nur mit Globs auszuwählen (dies ist einer der Gründe, warum ich zsh bevorzuge, wo die Globs so leistungsfähig sind, dass Sie sich nie um das Finden kümmern müssen).

Übelsuppe
quelle
3
Ich würde behaupten , dass das Parsen die Ausgabe findist nie richtig. Es gibt kein Szenario, in dem das Parsen der Ausgabe von findeffektiver ist als -execoder -execdir, oder unter sehr begrenzten Umständen,find … -print0 | xargs -0 …
Kojiro
@ Kojiro Ich bin anderer Meinung. Ihr Kommentar ist zu naiv. Für sich wiederholende Vorgänge in großen Ergebnislisten oder Suchlisten, die aus großen Dateisystemen stammen und deren Suche lange dauert, ist es vorteilhaft, die Ausgabe von find zu speichern und in separaten Routinen zu verarbeiten, damit Sie den Befehl find oder push nicht wiederholen müssen die Grenzen der einzeiligen Skripterstellung. Und das ist nur ein Beispiel, bei dem Ihre Argumentation auseinander fällt. Da sind mehr.
Ian C.
1
@ IanC. Zwischenspeichern Sie also die Ausgabe von find … -print0. Auch ich behaupte , dass eine Liste von Dateinamen aufpoliert Cachen ist , die Grenzen des einzeiligen Scripting schieben. Das Problem der Cache-Ungültigmachung ist ebenfalls nicht unerheblich.
Kojiro
Vielen Dank für den Vorschlag, eine while-Schleife zu verwenden - sie funktioniert tatsächlich besser (dh sie funktioniert). Das Problem bei der Verwendung der Glob-Erweiterung besteht darin, dass sie nicht mit "versteckten" Verzeichnissen (wie z. B. .Testdir) funktioniert, während FIND dies tut.
JJarava
@JJarava Aktivieren Sie einfach die shopt -s dotglobGlob-Erweiterung mit gepunkteten Namen.
Kojiro
3

Ich habe zwei Vorschläge:

  1. Verwenden Sie seddiese Option, um alle Verzeichnisnamen in Anführungszeichen zu setzen.
  2. Pipe to xargsmit Argument -L 1. Dadurch wird in jeder Zeile von stdin ein Befehl ausgeführt, wodurch die forSchleife vermieden wird.

Versuchen Sie diese Pipeline:

find . -maxdepth 1 -mindepth 1 -type d | cut -c 3- | sed 's/.*/"&"/' | xargs -L 1 chmod u+x

Flavin
quelle
Eigentlich möchte ich Find direkt nutzen, um die Änderung von Berechtigungen direkt
vorzunehmen
2

Der Kern des Problems besteht darin, dass bei der Befehlsersetzung eine Wortaufteilung erfolgt , sodass Namen mit Leerzeichen nicht vollständig von separaten Namen unterschieden werden können. Globbing leidet nicht unter dieser Schwierigkeit. Wenn es also eine Möglichkeit gibt, Verzeichnisse mit einem Glob zu identifizieren und die Befehlsersetzung vollständig zu vermeiden, sind Sie frei zu Hause. Und da ist:

chmod -x *
chmod u+x */

Aber auch das ist zu viel Arbeit, da chmoddas Xsymbolische Flag (Großbuchstabe X) das ausführbare Bit nur auf Verzeichnisse und Dateien anwendet, die bereits ausführbar sind. Sie können also wirklich einfach Folgendes tun:

chmod -x *
chmod u+X *
Kojiro
quelle
1

Entweder zieht Ihr Alias ​​nur das erste Bit vor dem Leerzeichen oder Ihre for-Schleife liest nur das erste Bit

Sie können dies testen, indem Sie Ihren Befehlen einige Testkommentare hinzufügen. Ich habe den Alias ​​so beschränkt, dass nur das zuletzt gefundene Verzeichnis abgerufen wird, um die Iterationen auf 1 zu isolieren. Ich habe ausgedruckt, was der Alias ​​für empfänglich hält, und dann den Inhalt der Variablen wiederholt, um sicherzustellen Sie passen:

jaravj$ alias getdirs='find . -maxdepth 1 -mindepth 1 -type d | tail -1|  cut -c 3-'
jaravj$ getdirs
jaravj$ for i in $(getdirs); do echo "Filename is "$i; chmod u+x $i; done

BEARBEITEN: geändert do; echo ...in, do echo ...da die Ausführung der Zeile verhindert wurde

stuffe
quelle
Versuchte Ihren Vorschlag und die Ausgabe ist wie erwartet: Ich habe einen Ordner mit dem Namen "ZZPRODUCT-Datenordner", daher lautet die Ausgabe des FOR: Dateiname ist ZZPRODUCT chmod: ZZPRODUCT: Keine solche Datei oder kein solches Verzeichnis Dateiname ist Daten chmod: Daten: Keine solche Datei oder Verzeichnis Dateiname ist Ordner chmod: Ordner: Keine solche Datei oder Verzeichnis
JJarava
@JJarava Die Ausgabe ist nicht wie erwartet, wenn Ihr Ordner benannt ist ZZPRODUCT data folder! Wenn es wie erwartet gewesen wäre, wäre die Ausgabe Filename is ZZPRODUCT data foldernicht so gewesen, Filename is ZZPRODUCTwie Sie es angegeben haben. Stuffe hat das Problem gefunden.
Ian C.
Wenn die Ausgabe des Echos abgeschnitten ist und die Ausgabe von getdirsnicht, befindet sich das Problem in der Schleife. Wenn getdirsdie Ausgabe abgeschnitten ist, liegt das Problem bei Ihrem Alias. Trotzdem gefällt mir die Lösung von @ patrix. Haben Sie es versucht?
stuffe