Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

find in shell cli != script (escaping Fehler?)

4 views
Skip to first unread message

Jan Novak

unread,
Aug 9, 2022, 5:34:00 AM8/9/22
to

Hallo,

ich (ver-)suche mir hier den Wolf und finde keine Lösung.

in einem Script in (bash 4.4.12(1)-release) wird u.a. das hier
festgelegt und ausgeführt:

dir="/irgendwo"
search="msk*" # auch nicht mit 'msk*' oder "msk\*"
opt="-type f"
lt=30

find "$dir" -name "$search" "$opt" -mtime +"$lt" -exec rm -rf '{}' \;

Lasse ich den Befehl per echo ausgeben und führe ihn auf der Shell aus,
klappts. Innerhalb des Scriptes aber nicht.

Fehlermeldung:
find: Der Pfad muss vor dem Ausdruck stehen: -type f

Bin n bisserl ratlos.

Jan

Laurenz Trossel

unread,
Aug 9, 2022, 5:50:07 AM8/9/22
to
On 2022-08-09, Jan Novak <rep...@gmail.com> wrote:

> opt="-type f"

"$opt" wird zu _einem_ Argument mit dem Inhalt "-type f".
$opt wir zu _zwei_ Argumenten, eins "-type", das andere "f".

Ulli Horlacher

unread,
Aug 9, 2022, 6:23:00 AM8/9/22
to
Jan Novak <rep...@gmail.com> wrote:

> dir="/irgendwo"
> search="msk*" # auch nicht mit 'msk*' oder "msk\*"
> opt="-type f"
> lt=30
>
> find "$dir" -name "$search" "$opt" -mtime +"$lt" -exec rm -rf '{}' \;
>
> Lasse ich den Befehl per echo ausgeben und führe ihn auf der Shell aus,
> klappts.

Siehst du da " ? Also...? :-)


> Innerhalb des Scriptes aber nicht.

Da sind auch " drin.
Tipp: Wozu sind die da, was machen die denn?

> Fehlermeldung:
> find: Der Pfad muss vor dem Ausdruck stehen: -type f

Germanisches locale: tststs!


--
Ullrich Horlacher Server und Virtualisierung
Rechenzentrum TIK
Universitaet Stuttgart E-Mail: horl...@tik.uni-stuttgart.de
Allmandring 30a Tel: ++49-711-68565868
70569 Stuttgart (Germany) WWW: http://www.tik.uni-stuttgart.de/

Jan Novak

unread,
Aug 9, 2022, 7:29:36 AM8/9/22
to
Am 09.08.22 um 11:50 schrieb Laurenz Trossel:
ok... und weiter? Ich verstehe leider nicht.
Soll ich das Leerzeichen Escapen? Hab ich gerade versucht, hilft nicht.

Ich brauche doch in der Kommandozeile für find das "-type f"
Stehe gerade wohl auf dem Schlauc ;-)

Jan

Jan Novak

unread,
Aug 9, 2022, 7:33:29 AM8/9/22
to
Am 09.08.22 um 12:22 schrieb Ulli Horlacher:
> Jan Novak <rep...@gmail.com> wrote:
>
>> dir="/irgendwo"
>> search="msk*" # auch nicht mit 'msk*' oder "msk\*"
>> opt="-type f"
>> lt=30
>>
>> find "$dir" -name "$search" "$opt" -mtime +"$lt" -exec rm -rf '{}' \;
>>
>> Lasse ich den Befehl per echo ausgeben und führe ihn auf der Shell aus,
>> klappts.
>
> Siehst du da " ? Also...? :-)
>
>
>> Innerhalb des Scriptes aber nicht.
>
> Da sind auch " drin.
> Tipp: Wozu sind die da, was machen die denn?
>
>> Fehlermeldung:
>> find: Der Pfad muss vor dem Ausdruck stehen: -type f

Hallo Ulli,

so sehr ich deine Posts in den verschiedenen NG's schätze, so sehr sind
wir hier aber weder im Kindergarten noch in der Schule.

Ich habe mir sehr lange Gedanken gemacht, wo der Fehler liegen könnte
und hab zig Dinge versucht - ohne Erfolg. Da ich im Internet zur
Fehlermeldung auch nur das escapen des * als Lösungsvorschlag gefunden
habe, habe ich hier um Hilfe gebeten. Deine Antwort ist mir leider keine
Hilfe, auch wenns wohl gut gemeint war.

Jan

Ulli Horlacher

unread,
Aug 9, 2022, 8:18:27 AM8/9/22
to
Jan Novak <rep...@gmail.com> wrote:
> Am 09.08.22 um 12:22 schrieb Ulli Horlacher:
> > Jan Novak <rep...@gmail.com> wrote:
> >
> >> dir="/irgendwo"
> >> search="msk*" # auch nicht mit 'msk*' oder "msk\*"
> >> opt="-type f"
> >> lt=30
> >>
> >> find "$dir" -name "$search" "$opt" -mtime +"$lt" -exec rm -rf '{}' \;
> >>
> >> Lasse ich den Befehl per echo ausgeben und führe ihn auf der Shell aus,
> >> klappts.
> >
> > Siehst du da " ? Also...? :-)
> >
> >
> >> Innerhalb des Scriptes aber nicht.
> >
> > Da sind auch " drin.
> > Tipp: Wozu sind die da, was machen die denn?
> >
>
> so sehr ich deine Posts in den verschiedenen NG's schätze, so sehr sind
> wir hier aber weder im Kindergarten noch in der Schule.

Du willst also nichts dazulernen und eine fertige Loesung haben ohne sie zu
verstehen?
Sorry, gibts bei mir nicht. Ich erwarte minimales Mitdenken und
beantworten meiner Nachfragen.
Ich habe dir Hilfestellung gegeben um deinen Fehler zu verstehen und um
selber zur Loesung zu kommen.
Du kannst das ignorieren, dann kommst du halt nicht weiter.

Jan Novak

unread,
Aug 9, 2022, 8:44:58 AM8/9/22
to
Am 09.08.22 um 14:18 schrieb Ulli Horlacher:
> Jan Novak <rep...@gmail.com> wrote:
>> Am 09.08.22 um 12:22 schrieb Ulli Horlacher:
>>> Jan Novak <rep...@gmail.com> wrote:
>>>
>>>> dir="/irgendwo"
>>>> search="msk*" # auch nicht mit 'msk*' oder "msk\*"
>>>> opt="-type f"
>>>> lt=30
>>>>
>>>> find "$dir" -name "$search" "$opt" -mtime +"$lt" -exec rm -rf '{}' \;
>>>>
>>>> Lasse ich den Befehl per echo ausgeben und führe ihn auf der Shell aus,
>>>> klappts.
>>>
>>> Siehst du da " ? Also...? :-)
>>>
>>>
>>>> Innerhalb des Scriptes aber nicht.
>>>
>>> Da sind auch " drin.
>>> Tipp: Wozu sind die da, was machen die denn?
>>>
>>
>> so sehr ich deine Posts in den verschiedenen NG's schätze, so sehr sind
>> wir hier aber weder im Kindergarten noch in der Schule.
>
> Du willst also nichts dazulernen und eine fertige Loesung haben ohne sie zu
> verstehen?
> Sorry, gibts bei mir nicht. Ich erwarte minimales Mitdenken und
> beantworten meiner Nachfragen.
> Ich habe dir Hilfestellung gegeben um deinen Fehler zu verstehen und um
> selber zur Loesung zu kommen.
> Du kannst das ignorieren, dann kommst du halt nicht weiter.

OT:
Also eine fertige Lösung habe ich nicht verlangt, nehme sie aber
natürlich gerne und ein Denkanstoss ist ebenso willkommen.
Übrigens lerne ich (und eventuell Andere) sicher auch was von einer
fertigen Lösung ;-) - deswegen hat stackoverflow/* auch so einen Zulauf
- dort gibt es als Antwort "nur" fertige Lösungen.
Dein Ansatz und Schreibform mag für andere hilfreich sein, für mich ist
das leider nicht passend.
Dennoch danke ich dir für die Mühe es versucht zu haben (und das ist
nicht sarkastisch/ironisch) gemeint.

Zum Thema:
Ich habe alle Strings in Anführungszeichen gesetzt und auch ohne
versucht. Das type -f habe ich ebenso in einfach Anführungszeichen
gesetzt, damit die Shell das nicht auswertet, auch den * - hat aber
alles nicht geholfen.

Jan

Enrik Berkhan

unread,
Aug 9, 2022, 11:13:05 AM8/9/22
to
Jan Novak <rep...@gmail.com> wrote:
> Ich habe alle Strings in Anführungszeichen gesetzt und auch ohne
> versucht. Das type -f habe ich ebenso in einfach Anführungszeichen
> gesetzt, damit die Shell das nicht auswertet, auch den * - hat aber
> alles nicht geholfen.

Man kann auch zu viele Anführungszeichen setzen. Es stand bereits in der
ersten Antwort, mit Begründung: lass die um $opt weg.

Gruß,
Enrik

Helmut Waitzmann

unread,
Aug 9, 2022, 12:45:37 PM8/9/22
to
Jan Novak <rep...@gmail.com>:
>Hallo,
>
>ich (ver-)suche mir hier den Wolf und finde keine Lösung.
>
>
>in einem Script in (bash 4.4.12(1)-release) wird u.a. das hier
>festgelegt und ausgeführt:
>
>dir="/irgendwo"
>search="msk*" # auch nicht mit 'msk*' oder "msk\*"
>opt="-type f"
>lt=30

Ich muss etwas raten:  Ist es deine Absicht, «find» aufzurufen und
dabei gewisse Parameter nicht direkt hinzuschreiben sondern sie
«verstellbar» zu halten, um beispielsweise das Startverzeichnis des
Suchbaums, das Dateinamen‐Suchmuster und den Datei‐Suchtyp erst vor
dem «find»‐Aufruf zu errechnen oder vom Anwender zu erfragen?

>find "$dir" -name "$search" "$opt" -mtime +"$lt" -exec rm -rf '{}' \;
>
>Lasse ich den Befehl per echo ausgeben und führe ihn auf der Shell
>aus, klappts.

Das sicher nicht, sondern genauer:  Lässest du den Befehl per echo
ausgeben und tippst dann selber ein, was dir «echo» ausgegeben hat,
dann klappts.

>Innerhalb des Scriptes aber nicht.
>

Ja sicher:  Innerhalb des Skripts hast du ja auch nicht das stehen,
was dir echo ausgegeben hat.

>Fehlermeldung:
>find: Der Pfad muss vor dem Ausdruck stehen: -type f


Die Ursache:


>dir="/irgendwo"
>search="msk*" # auch nicht mit 'msk*' oder "msk\*"
>opt="-type f"
>lt=30
>find "$dir" -name "$search" "$opt" -mtime +"$lt" -exec rm -rf '{}' \;

bewirkt, dass «find» mit folgenden Parametern aufgerufen wird:


«find», «/irgendwo», «-name», «msk*», «-type f», «-mtime», «+30»,
«-exec», «rm», «-rf», «{}» und «;»

Mit dem Parameter «-type f» kann «find» nichts anfangen, deshalb
nimmt es an, es könnte sich dabei um einen weiteren Pfad (wie
«/irgendwo») handeln und gibt dir in der Fehlermeldung einen
entsprechenden Hinweis (der in diesem Fall in die Irre führt).

Die von dir gewünschte Parameterliste für «find» müsste dagegen so
aussehen:

«find», «/irgendwo», «-name», «msk*», «-type», «f», «-mtime», «+30»,
«-exec», «rm», «-rf», «{}» und «;»

=> Statt des Parameters «-type f» gibt es zwei Parameter: «-type»
und «f».

Wenn du also die zu findenden Dateitypen per Shell‐Variable angeben
können willst, brauchst du zwei Shell‐Variablen, damit jede einen
der beiden Parameter liefert oder – da du sagst, du nimmst einen
«bash» als Shell – eine Feldvariable oder – wenn du zum
POSIX‐Standard kompatibel bleiben möchtest – die positional
parameters als Feldvariablenersatz.


Mit einer Feldvariablen im «bash» würde es so aussehen:


dir='/irgendwo'
search='msk*'
declare -a -- opt && opt=('-type' 'f')
lt=30
find "$dir" -name "$search" "${opt[@]}" -mtime +"$lt" \
-exec rm -rf -- '{}' \;


Zum POSIX‐Standard kompatibel würde es so aussehen:


dir='/irgendwo'
search='msk*'
set -- '-type' 'f'
lt=30
find "$dir" -name "$search" "$@" -mtime +"$lt" \
-exec rm -rf -- '{}' \;


Nebenbei:  Ich würde dir empfehlen, «rm» (und viele andere ähnliche
Kommandos) immer robust zu verwenden und deshalb hinter den Optionen
(hier: «-rf») ein ausdrückliches Optionenende «--» einzufügen:

rm -rf -- '{}' \;

Würdest du beispielsweise im «find»‐Kommando als
Dateinamensuchmuster eines angeben, das auch Dateinamen findet, die
mit einem Minuszeichen beginnen, bekäme «rm» ohne «--»
überraschende, möglicherweise zerstörerische Effekte.


Weitere Erwägungen:


Wenn du als Dateityp «-type» «d» angibst, sucht «find» nach
Verzeichnissen und lässt auf gefundene das Kommando

rm -rf -- gefundenes_Verzeichnis

los.  Das löscht das jeweilige Verzeichnis mitsamt seinen in ihm
enthaltenen Dateien und Unterverzeichnissen.  Infolge dessen wird
find, das zuvor möglicherweise mitbekommen hat, dass im gefundenen
Verzeichnis Unterverzeichnisse enthalten sind, nach dem Löschen
des Verzeichnisses sehr überrascht sein, dass weder das Verzeichnis
noch darin vermutete Unterverzeichnisse noch vorhanden sind.

Da solltest du «find» mitteilen, dass es in gefundene zu löschende
Verzeichnisse nicht mehr hineinsteigen soll.  Dazu gibt es die
«find»‐Aktion «-prune».  («-prune» ist bei gefundenen
Nicht‐Verzeichnissen nicht schädlich, kann also bei zu löschenden
Dateinamen – egal, ob sie nun ein Verzeichnis sind oder nicht –
immer angegeben werden.).

Hier folgt nochmal die «bash»‐Fassung; in der POSIX‐Fassung geht es
entsprechend:

dir='/irgendwo'
search='msk*'
declare -a -- opt && opt=('-type' 'f')
lt=30
find "$dir" -name "$search" "${opt[@]}" -mtime +"$lt" \
-prune -exec rm -rf -- '{}' \;


Noch eine Bemerkung zur Variablen «lt»:  Ich weiß ja nicht, wofür
der Name «lt» steht.  Jedenfalls bewirken die «find»‐Parameter
«-mtime» und «+30», dass «find» nur (Nicht‐)Verzeichnisse in
Betracht zieht, die seit mindestens 31 Tagen (genauer ausgedrückt:
seit mindestens 31*24*60*60 Sekunden) nicht mehr verändert worden
sind.

--
Hat man erst verstanden, wie Unix funktioniert, ist auch
das Shell-Handbuch kein Buch mit sieben Siegeln mehr.

Marcel Logen

unread,
Aug 9, 2022, 1:20:57 PM8/9/22
to
Helmut Waitzmann in de.comp.os.unix.shell:

>Mit dem Parameter «-type f» kann «find» nichts anfangen, deshalb
>nimmt es an, es könnte sich dabei um einen weiteren Pfad (wie
>«/irgendwo») handeln und gibt dir in der Fehlermeldung einen
>entsprechenden Hinweis (der in diesem Fall in die Irre führt).

Mit meinem OpenBSD-find gibt es folgende Fehlermeldung:

find: -type f: unknown option

Marcel hvuk (589780)
--
╭─────╮ ╭─────╮ ╭───────╮ ╭─╮ ╭──╮
───╮ ╰───╮ ╰─╯ ╭──╯ ╰────╮ ╰─────╮ ╭─╯ │ ╭───╯ │ ╭────╮
╭─╯ ╭───╯ ╭───╯ ╭─╮ ╭─╮ ╰──────╮ │ │ │ │ ╭──╯ ╭─────╯ ╰─
╰────╯ ╰───────╯ ╰──╯ ╰─────────╯ ╰─╯ ╰──╯ ╰─────╯ a04659

Ulli Horlacher

unread,
Aug 9, 2022, 3:40:28 PM8/9/22
to
Jan Novak <rep...@gmail.com> wrote:

> >> so sehr ich deine Posts in den verschiedenen NG's schätze, so sehr sind
> >> wir hier aber weder im Kindergarten noch in der Schule.
> >
> > Du willst also nichts dazulernen und eine fertige Loesung haben ohne sie zu
> > verstehen?
> > Sorry, gibts bei mir nicht. Ich erwarte minimales Mitdenken und
> > beantworten meiner Nachfragen.
> > Ich habe dir Hilfestellung gegeben um deinen Fehler zu verstehen und um
> > selber zur Loesung zu kommen.
> > Du kannst das ignorieren, dann kommst du halt nicht weiter.
>
> Dein Ansatz und Schreibform mag für andere hilfreich sein, für mich ist
> das leider nicht passend.

Kein Problem, dann spar ich mir eben in Zukunft dir zu antworten.


> Ich habe alle Strings in Anführungszeichen gesetzt und auch ohne
> versucht.

Ohne " funktionierts.

> Das type -f habe ich ebenso in einfach Anführungszeichen
> gesetzt, damit die Shell das nicht auswertet

Die Shell wertet das nicht aus, weil das keine Metazeichen enthaelt.
Deshalb sind hier Anfuehrungszeichen nicht nur ueberfluessig, sondern
sogar kontraproduktiv: es funktioniert dann nicht.

Christian Garbs

unread,
Aug 9, 2022, 3:59:18 PM8/9/22
to
Mahlzeit!

Jan Novak <rep...@gmail.com> wrote:

> in einem Script in (bash 4.4.12(1)-release) wird u.a. das hier
> festgelegt und ausgeführt:
>
> dir="/irgendwo"
> search="msk*" # auch nicht mit 'msk*' oder "msk\*"
> opt="-type f"
> lt=30
>
> find "$dir" -name "$search" "$opt" -mtime +"$lt" -exec rm -rf '{}' \;
>
> Lasse ich den Befehl per echo ausgeben und führe ihn auf der Shell aus,
> klappts. Innerhalb des Scriptes aber nicht.
>
> Fehlermeldung:
> find: Der Pfad muss vor dem Ausdruck stehen: -type f
>
> Bin n bisserl ratlos.

Die Lösung kam ja schon.
Um sowas zu debuggen, bietet sich "set -x" an.
Dann wird Dir Zeile zusätzlich nochmal zur Kontrolle ausgegeben - und
zwar im expandierten Zustand (also eingesetzte Variablen, aufgelöstes
*.bak und sowas).

Insbesondere wegen dabei (anders als bei echo) einfache
Anführungszeichen gesetzt, so dass Du siehst, wo Argumente starten und
enden.

Wenn Du dann die Ausgabe des Skripts der von der Kommandozeile
vergleichst, kannst Du den Unterschied entdecken.

(Auch wenn der hier, weil es sich vermutlich nur um zwei einfache
Anführungszeichen handelt, schwer zu entdecken ist - es ist deutlich
einfacher, wenn man schon weiß, wohin man gucken muss ;-)

Ist wie mit der Fehlermeldung: Wenn man den Fehler kennt, ist es ein
Fall von "steht da doch": find interpretiert "-type f" als Pfad und
da liegt der Hund begraben.)

Gruß
Christian

PS: Nicht wundern wenn auf der Kommandozeile noch diverse Zeilen
hinterherkommen: Wenn Du einen Prompt definiert hast, in dem dynamisch
Dinge ausgewertet werden, werden auch diese mit debuggt. Dagegen
hülfe "set -x; find ...; set +x" als eine Zeile - das schaltet das
Debugging nach dem find gleich wieder ab.
--
....Christian.Garbs....................................https://www.cgarbs.de
How come wrong numbers are never busy?

Helmut Waitzmann

unread,
Aug 9, 2022, 5:04:58 PM8/9/22
to
Marcel Logen <33320000...@ybtra.de>:
>Helmut Waitzmann in de.comp.os.unix.shell:
>
>>Mit dem Parameter «-type f» kann «find» nichts anfangen, deshalb
>>nimmt es an, es könnte sich dabei um einen weiteren Pfad (wie
>>«/irgendwo») handeln und gibt dir in der Fehlermeldung einen
>>entsprechenden Hinweis (der in diesem Fall in die Irre führt).
>
>Mit meinem OpenBSD-find gibt es folgende Fehlermeldung:
>
>
> find: -type f: unknown option

Jetzt habe ich auch mal ausprobiert, was das Shell‐Kommando


find /irgendwo -name 'msk*' '-type f' -mtime '+30' -print


für eine Fehlermeldung spuckt, und erhalte (mit GNU‐find)


find: unbekannte Option `-type f'


Vermutung:  Jan hat uns ein X für ein U vorgemacht, indem er uns
seinen «find»‐Programmaufruf nicht so gezeigt hat, wie er wirklich
geschieht.

Die allgemeine Regel «Versuche, ein Minimalbeispiel, das das von dir
beobachtete Verhalten zeigt, zu präsentieren» ist zwar gut,
erfordert aber, dass man dieses Minimalbeispiel auch selber probiert
und die davon erhaltene Fehlermeldung zeigt.


Bingo!  Die Shell‐Kommandozeile


find /irgendwo -name 'msk*' ' -type f' -mtime '+30' -print

spuckt bei mir in der Fehlermeldung als erste Zeile folgenden Text:


find: Der Pfad muß vor dem Suchkriterium stehen: -type f

Wenn man genau hinschaut, sieht man, dass hier, wie auch in der von
Jan gezeigten Fehlermeldung zwischen dem zweiten Doppelpunkt und dem
«-type» zwei Leerstellen stehen.  Zwei, nicht eine!  Dabei reicht
doch eine einzige vollkommen.  Folgerung:  Die zweite Leerstelle
rührt vom bemängelten «find»‐Parameter her!

Jan, wenn du Hilfe haben möchtest, sei wenigstens in der
Beschreibung des Problems sorgfältig, sonst wird das nichts!

Marcel, ich danke für den Gedankenanstoß.

Marcel Logen

unread,
Aug 9, 2022, 6:30:36 PM8/9/22
to
Helmut Waitzmann in de.comp.os.unix.shell:

>Bingo!  Die Shell‐Kommandozeile
>
> find /irgendwo -name 'msk*' ' -type f' -mtime '+30' -print
>
>spuckt bei mir in der Fehlermeldung als erste Zeile folgenden Text:
>
> find: Der Pfad muß vor dem Suchkriterium stehen: -type f
>
>Wenn man genau hinschaut, sieht man, dass hier, wie auch in der von Jan
>gezeigten Fehlermeldung zwischen dem zweiten Doppelpunkt und dem «-type»
>zwei Leerstellen stehen.  Zwei, nicht eine!  Dabei reicht doch eine
>einzige vollkommen.  Folgerung:  Die zweite Leerstelle rührt vom
>bemängelten «find»‐Parameter her!

Mein Debian11-System zeigt folgendes Verhalten:

| user14@n14:~$ cat /etc/debian_version
| 11.4

| user14@n14:~$ find --version | head -n 1
| find (GNU findutils) 4.8.0

| user14@n14:~$ find /tmp -maxdepth 1 -name 'msk*' "-type f" -print
| find: unbekannte Option »-type f«

| user14@n14:~$ find /tmp -maxdepth 1 -name 'msk*' " -type f" -print
| find: paths must precede expression: ` -type f'

| user14@n14:~$ find /tmp -maxdepth 1 ' -type f' -name 'msk*' -print
| find: paths must precede expression: ` -type f'

| user14@n14:~$ find /tmp ' -type f' -maxdepth 1 -name 'msk*' -print
| /tmp/msk06
| /tmp/msk08
| /tmp/msk10
| /tmp/msk09
| /tmp/msk04
| /tmp/msk05
| /tmp/msk07
| /tmp/msk11
| /tmp/msk03
| find: ‘ -type f’: Datei oder Verzeichnis nicht gefunden
| user14@n14:~$

Das heißt für mich, daß das ' -type f' nicht als Expression oder
Option angesehen wird, weil es nicht mit einem Minuszeichen beginnt.
Es wird als Path interpretiert, wie man an der letzten Meldung
sehen kann.

Marcel j6q6 (629574)
--
╭──╮ ╭──────╮ ╭─╮ ╭───╮ ╭───╮ ╭──╮
╭────╯ ╰────╮ ╭───╯ ╭───╯ ╭─╯ ╰──╯ │ ╭─╯ ╰──╮ ╭──╯ │
────╯ ╭─────╯ │ ╰─────╯ ╰──╯ ╭─────╯ ╭─╯ ╭─╯ ╭──
╰───────╯ ee0453 ╰───────╯ ╰─────╯

Helmut Waitzmann

unread,
Aug 10, 2022, 3:42:38 AM8/10/22
to
Marcel Logen <33320000...@ybtra.de>:
>Helmut Waitzmann in de.comp.os.unix.shell:
>
>>Bingo!  Die Shell‐Kommandozeile
>>
>>
>> find /irgendwo -name 'msk*' ' -type f' -mtime '+30' -print
>>
>>spuckt bei mir in der Fehlermeldung als erste Zeile folgenden
>>Text:
>>
>> find: Der Pfad muß vor dem Suchkriterium stehen: -type f
>>
>>Wenn man genau hinschaut, sieht man, dass hier, wie auch in der
>>von Jan gezeigten Fehlermeldung zwischen dem zweiten Doppelpunkt
>>und dem «-type» zwei Leerstellen stehen.  Zwei, nicht eine!  Dabei
>>reicht doch eine einzige vollkommen.  Folgerung:  Die zweite
>>Leerstelle rührt vom bemängelten «find»‐Parameter her!
>
>Mein Debian11-System zeigt folgendes Verhalten:
>
>
>| user14@n14:~$ cat /etc/debian_version
>| 11.4
>
>| user14@n14:~$ find --version | head -n 1
>| find (GNU findutils) 4.8.0
>

[…]

>| user14@n14:~$ find /tmp -maxdepth 1 -name 'msk*' " -type f" -print
>| find: paths must precede expression: ` -type f'
>
>| user14@n14:~$ find /tmp -maxdepth 1 ' -type f' -name 'msk*' -print
>| find: paths must precede expression: ` -type f'

Ah, das ist gut:  Eine hinreichend neue Fassung von «find» setzt den
bemängelten Parameter in (einfache) Anführungszeichen.  Damit wird
man besser mit der Nase drauf gestoßen, wo der Hund begraben liegt.

>
>| user14@n14:~$ find /tmp ' -type f' -maxdepth 1 -name 'msk*' -print
>| /tmp/msk06

[…]

>| /tmp/msk03
>| find: ‘ -type f’: Datei oder Verzeichnis nicht gefunden
>| user14@n14:~$
>
>Das heißt für mich, daß das ' -type f' nicht als Expression oder
>Option angesehen wird, weil es nicht mit einem Minuszeichen beginnt.
>Es wird als Path interpretiert, wie man an der letzten Meldung
>sehen kann.

Genau.

Jan Novak

unread,
Aug 10, 2022, 3:51:46 AM8/10/22
to
Am 09.08.22 um 18:44 schrieb Helmut Waitzmann:
> Jan Novak <rep...@gmail.com>:
> Hier folgt nochmal die «bash»‐Fassung; in der POSIX‐Fassung geht es
> entsprechend:
>
> dir='/irgendwo'
> search='msk*'
> declare -a -- opt && opt=('-type' 'f')
> lt=30
> find "$dir" -name "$search" "${opt[@]}" -mtime +"$lt" \
> -prune -exec rm -rf -- '{}' \;
>
>
> Noch eine Bemerkung zur Variablen «lt»:  Ich weiß ja nicht, wofür
> der Name «lt» steht.  Jedenfalls bewirken die «find»‐Parameter
> «-mtime» und «+30», dass «find» nur (Nicht‐)Verzeichnisse in
> Betracht zieht, die seit mindestens 31 Tagen (genauer ausgedrückt:
> seit mindestens 31*24*60*60 Sekunden) nicht mehr verändert worden
> sind.


Hallo Helmut,

vielen dank für diese ausführliche Erklärung und warum das so ist, wie
es ist. Unter anderem mit dem -- nach dem rm -rf war mir nicht bekannt,
oder auch nicht die "prune" option.
Dadurch erklärt sich auch einige Fehlerausgaben des find mit rm in der
letzten Zeit.

Nochmals: sehr gute Hilfe (für mich und vermutlich alle anderen).

Jan

Jan Novak

unread,
Aug 10, 2022, 3:54:58 AM8/10/22
to
Am 09.08.22 um 23:03 schrieb Helmut Waitzmann:
> Jan, wenn du Hilfe haben möchtest, sei wenigstens in der Beschreibung
> des Problems sorgfältig, sonst wird das nichts!

Ich habe nicht absichtlich ein zweites Leerzeichen eingegeben, habe aber
auch nicht darauf geachtet, da ich der Meinung war, dass es egal wäre.
OK, wieder was gelernt: es ist nicht egal.

Jan

Jan Novak

unread,
Aug 10, 2022, 3:57:45 AM8/10/22
to
Am 09.08.22 um 21:59 schrieb Christian Garbs:
> Mahlzeit!
> Um sowas zu debuggen, bietet sich "set -x" an.
> Dann wird Dir Zeile zusätzlich nochmal zur Kontrolle ausgegeben - und
> zwar im expandierten Zustand (also eingesetzte Variablen, aufgelöstes
> *.bak und sowas).
> Insbesondere wegen dabei (anders als bei echo) einfache
> Anführungszeichen gesetzt, so dass Du siehst, wo Argumente starten und
> enden.
> Wenn Du dann die Ausgabe des Skripts der von der Kommandozeile
> vergleichst, kannst Du den Unterschied entdecken.

ebenfalls guter Tipp. Werde ich in Zukunft machen.
Danke dafür.

Jan

Helmut Waitzmann

unread,
Aug 10, 2022, 8:36:59 AM8/10/22
to
Jan Novak <rep...@gmail.com>:
>Am 09.08.22 um 23:03 schrieb Helmut Waitzmann:

>> Jan, wenn du Hilfe haben möchtest, sei wenigstens in der
>> Beschreibung des Problems sorgfältig, sonst wird das nichts!
>
>Ich habe nicht absichtlich ein zweites Leerzeichen eingegeben,
>

Das glaub' ich dir gerne.


>habe aber auch nicht darauf geachtet, da ich der Meinung war, dass
>es egal wäre. OK, wieder was gelernt: es ist nicht egal.

Der Witz ist, dass die Fehlermeldung, die du gezeigt hast, nicht zum
Kommando passt, das du gezeigt hast, sondern (zufällig) zu einem
anderen, das du nicht gezeigt hast.  Was soll man als jemand, der
hinter das Problem kommen will, dann machen?  Wenn man's nicht
merkt, so, wie ich, gerät man auf eine falsche Fährte und hängt sich
möglicherweise umsonst rein.  Wenn man's merkt, wie Marcel, erkennt
man die Sackgasse wenigstens gleich, muss dann aber nachhaken, ehe
man sich reinhängen kann.

Ich bin stillschweigend davon ausgegangen, dass du die Fehlermeldung
per Copy&Paste so, wie du sie erhalten hast, in deinen Newsbeitrag
kopiert hast.  Falls du sie jedoch abgetippt hast, glaub' ich dir
gerne, dass du nicht absichtlich ein zweites Leerzeichen eingefügt
hast, möchte dich aber bitten, dass du in solchen Fällen extra
sorgfältig abtippst, um unabsichtliche Veränderungen zu vermeiden.

Ich habe keinen Thunderbird laufen, kann daher nicht ausprobieren,
ob man mit der Maus die Fehlermeldung, die man in einem Terminal
sieht, fassen und im Nachrichteneditor des Thunderbirds fallen
lassen kann.

Falls du das Programm «xclip» installiert hast, kannst du auch
vorgehen, wie das Beispiel zeigt:

{
dir="/irgendwo"
search="msk*"
opt="-type f"
lt=30
find "$dir" -name "$search" "$opt" -mtime +"$lt" \
-exec rm -rf '{}' \;
} 2>&1 | xclip -in -selection clipboard -filter

Das bewirkt, dass alles, was die Kommandos zwischen den
Schweifklammern normalerweise nur in den Standardausgabekanal oder
in den Fehlerausgabekanal (hier also aufs Terminal) schreiben
würden, wegen «xclip» sowohl ins Clipboard als auch aufs Terminal
kommt.  Dann kannst du im Thunderbird die Schreibmarke im zu
verfassenden Beitrag an die gewünschte Stelle stellen und mit dem
Tastendruck Control-V die Ausgabe einfügen.

Probier mal, ob du mit dem Shell‐Kommando


{
printf '%s\n' 'Hello, world!'
} 2>&1 | xclip -in -selection clipboard -filter

den Gruß in den Nachrichteneditor des Thunderbirds mit Control-V
einfügen kannst.

Jan Novak

unread,
Aug 10, 2022, 10:00:01 AM8/10/22
to

Am 10.08.22 um 14:36 schrieb Helmut Waitzmann:
>> habe aber auch nicht darauf geachtet, da ich der Meinung war, dass es
>> egal wäre. OK, wieder was gelernt: es ist nicht egal.
>
> Der Witz ist, dass die Fehlermeldung, die du gezeigt hast, nicht zum
> Kommando passt, das du gezeigt hast, sondern (zufällig) zu einem
> anderen, das du nicht gezeigt hast.  Was soll man als jemand, der hinter
> das Problem kommen will, dann machen?  Wenn man's nicht merkt, so, wie
> ich, gerät man auf eine falsche Fährte und hängt sich möglicherweise
> umsonst rein.  Wenn man's merkt, wie Marcel, erkennt man die Sackgasse
> wenigstens gleich, muss dann aber nachhaken, ehe man sich reinhängen kann.

Das war mir wirklich nicht bewusst.
Ich werde in Zukunft darauf achten. Wie du richtig vermutet hast, ist
der Teil den ich gepostet habe, nur ein ganz Kleiner des gesamten
Scripts. Da ist das dann wohl passiert (wollte es so einfach und klar
wie möglich machen).


> Falls du das Programm «xclip» installiert hast, kannst du auch vorgehen,
> wie das Beispiel zeigt:
>
>   {
>     dir="/irgendwo"
>     search="msk*"
>     opt="-type f"
>     lt=30
>     find "$dir" -name "$search" "$opt" -mtime +"$lt" \
>       -exec rm -rf '{}' \;
>   } 2>&1 | xclip -in -selection clipboard -filter
>
> Das bewirkt, dass alles, was die Kommandos zwischen den Schweifklammern
> normalerweise nur in den Standardausgabekanal oder in den
> Fehlerausgabekanal (hier also aufs Terminal) schreiben würden, wegen
> «xclip» sowohl ins Clipboard als auch aufs Terminal kommt.  Dann kannst
> du im Thunderbird die Schreibmarke im zu verfassenden Beitrag an die
> gewünschte Stelle stellen und mit dem Tastendruck Control-V die Ausgabe
> einfügen.

Ahhh... sehr interessant. xclip verwende ich für 3 Hotkeys, welche ich
im Thunderbirg häufig brauche. Dass ich das auch als pipe nutzen kann,
wusste ich nicht.


>
> Probier mal, ob du mit dem Shell‐Kommando
>
>   {
>     printf '%s\n' 'Hello, world!'
>   } 2>&1 | xclip -in -selection clipboard -filter



Hello, world!


:-)

Jan

Helmut Waitzmann

unread,
Aug 10, 2022, 10:51:36 AM8/10/22
to
Jan Novak <rep...@gmail.com>:

>Unter anderem mit dem -- nach dem rm -rf war mir nicht bekannt,
>

«--» gibt es bei vielen Programmen.  Ob es beim jeweiligen Programm
so ist, müsste jeweils im Handbuch drinstehen.

«--» stellt sicher, dass alle nach ihm folgenden Parameter keine
Optionen mehr sind, auch dann nicht, wenn sie mit einem Minuszeichen
beginnen.

Beispielsweise kann eine Datei durchaus den Namen «-f» haben.  Wenn
im Arbeitsverzeichnis beispielsweise Dateien mit den Namen
«unwichtig», «wichtig!» und «-f» liegen und man dann das
Kommando

rm -i *

laufen lässt, setzt der Shell daraus die
Programmaufruf‐Parameterliste bestehend aus «rm», «-i« und den
alphabetisch sortierten Dateinamen «-f», «unwichtig» und «wichtig!»
zusammen.  Das bewirkt einen Programmaufruf für «rm» wie
beispielsweise die folgende Kommandozeile:

rm -i -f unwichtig 'wichtig!'

Das kann man sehen, wenn man, wie von Christian empfohlen, zuvor das
Kommando

set -x

laufen lässt.


Dadurch wird man nicht, wie wegen «-i» beabsichtigt, bei jeder Datei
gefragt, ob sie gelöscht werden soll, sondern «rm» löscht wegen «-f»
ungefragt alles, was es löschen kann – auch schreibgeschützte
Dateinamen.


Würde man jedoch die Kommandozeile


rm -i -- *

schreiben, würde der Shell die Programmaufruf‐Parameterliste
bestehend aus «rm», «-i», «--», «-f», «unwichtig» und
«wichtig!» zusammenstellen.  Das bewirkt einen Programmaufruf wie
beispielsweise die folgende Kommandozeile:

rm -i -- -f unwichtig 'wichtig!'

Und da teilt der Parameter «--» dem «rm» mit, dass «-f» (und alle
folgenden Parameter) keine Optionen mehr sind, selbst dann nicht,
wenn sie so aussehen.  Die Folge:  «rm» fragt brav bei jedem der 3
Dateinamen, ob es ihn entfernen soll.

>oder auch nicht die "prune" option. Dadurch erklärt sich auch
>einige Fehlerausgaben des find mit rm in der letzten Zeit.

Ich kann dir für «find» empfehlen, auch die «POSIX»‐Dokumentation
(<http://pubs.opengroup.org/onlinepubs/9699919799/utilities/find.html#top>)
zu lesen.  Obwohl man erwarten könnte, dass die sehr trocken und
schwer zu verstehen ist, habe ich oft den Eindruck, das Gegenteil
ist der Fall.

Ein Beispiel:  Im GNU‐find‐Handbuch liest man zum Prädikat «-mtime»
folgende Erklärung, hier eingerückt mit uneingerückten Kommentaren
von mir:

-- Test: -mtime n
True if the file was last modified N*24 hours ago.

Dieser erste Satz klingt für mich, als meine er nur Dateien, die vor
exakt N*24 Stunden das letzte Mal geändert worden sind.  Dass das
nicht alles sein kann, merkt man dann, wenn man vom Abrunden liest:

The number of 24-hour periods since the file's
timestamp is always rounded down;

Aber dann stellt sich sofort die Frage, wohin wird abgerundet?  Auf
ganze Stunden, ganze Tage?

therefore 0 means "less than 24 hours ago", 1 means
"between 24 and 48 hours ago", and so forth.
Fractional values are supported but this only really
makes sense for the case where ranges ('+N' and '-N')
are used.

Aufgrund der Beispiele dämmert es mir dann, dass auf ganze Tage
abgerundet wird.

Also ich finde das viel weniger klar als das POSIX‐Handbuch, wenn
man weiß, dass 60*60*24 = 86400, die Anzahl der Sekunden eines
Tages, ist und dass «initialization time» der Startzeitpunkt des
«find»‐Programmaufrufs ist:

-mtime n
The primary shall evaluate as true if the file
modification time subtracted from the initialization
time, divided by 86400 (with any remainder
discarded), is n.

Da ist die Vorgehensweise klar:  Beispielsweise bedeutet -mtime 5,
dass die vergangene Zeit seit der letzten Dateiänderung in Sekunden
gemessen, ganzzahlig durch 86400, also durch die Anzahl der Sekunden
je Tag, geteilt, der beim ganzzahligen Teilen übrig gebliebene Rest
weggeworfen und das ganzzahlige Ergebnis betrachtet wird.  Ist es 5,
ist die Datei zwischen einschließlich exakt 5 Tagen und
ausschließlich exakt 6 Tagen alt, und der Dateiname passt auf das
Prädikat -mtime 5.

Marcel Logen

unread,
Aug 10, 2022, 11:53:55 AM8/10/22
to
Helmut Waitzmann in de.comp.os.unix.shell:

>«--» gibt es bei vielen Programmen.  Ob es beim jeweiligen Programm
>so ist, müsste jeweils im Handbuch drinstehen.
>
>«--» stellt sicher, dass alle nach ihm folgenden Parameter keine
>Optionen mehr sind, auch dann nicht, wenn sie mit einem Minuszeichen
>beginnen.

Zu diesem Thema fand ich gestern in man find (GNU, v4.8.0):

<https://manpages.debian.org/bullseye/findutils/find.1.en.html>

| This manual page talks about `options' within the expression list.
| These options control the behaviour of find but are specified
| immediately after the last path name. The five `real' options -H,
| -L, -P, -D and -O must appear before the first path name, if at
| all. A double dash -- could theoretically be used to signal that
| any remaining arguments are not options, but this does not really
| work due to the way find determines the end of the following path
| arguments: it does that by reading until an expression argument
| comes (which also starts with a `-'). Now, if a path argument
| would start with a `-', then find would treat it as expression
| argument instead. Thus, to ensure that all start points are taken
| as such, and especially to prevent that wildcard patterns
| expanded by the calling shell are not mistakenly treated as
| expression arguments, it is generally safer to prefix wildcards
| or dubious path names with either `./' or to use absolute path
| names starting with '/'.

Es ist also trotz/bei Verwendung von "--" mit "find" noch Vor-
sicht geboten, wenn ich das richtig verstehe.

Marcel k6ip (662105)
--
╭────╮ ╭─────╮ ╭────
╭───╮ ╭─╯ ╭─╯ ╰───╮ ╰─╮ ╭───╯
─╯ │ │ ╭─╯ ╭─────╮ ╭───╮ ╭─╮ ╭──╮ │ ╰──╮ ╭─╮ ╰─╮
╰──╯ ╰────╯ ╰───────╯ ╰──╯ ╰──╯ ╰──╯ ╰──╯ ╰────╯c84e05

Helmut Waitzmann

unread,
Aug 10, 2022, 3:55:09 PM8/10/22
to
Marcel Logen <33320000...@ybtra.de>:
Ja, du verstehst das richtig.  Die Schwierigkeit bei «find» ist,
dass es da drei Sorten von Aufrufparametern gibt: Optionen, Pfade
und der Suchausdruck (mit Prädikaten und Aktionen).

Optionen können von den folgenden Parametern mit «--» abgetrennt
werden.

Aber dann hat man noch die Aufgabe, die nach den Optionen folgenden
Pfade von dem anschließend folgenden Suchausdruck abzutrennen.

«find» macht die Unterscheidung darin, dass der Suchausdruck
entweder mit einem Prädikat oder einer Aktion oder mit einer
öffnenden runden Klammer beginnt, und fordert deshalb, dass die
Pfade weder mit einem Minuszeichen noch einer runden öffnenden
Klammer beginnen.

Diese Forderung an die Pfade kann man erfüllen, denn Pfade sind
entweder absolut oder relativ.  Ein absoluter Pfad beginnt mit einem
«/» (also weder mit einem «-» noch einem «(»); und an jeden
relativen Pfad kann man vorne (wo möglicherweise ein störendes «-»
oder «(» steht) «./» drankleben, ohne dass sich seine Bedeutung
ändert.

Wenn nun aber Pfade nicht mit einem Minuszeichen beginnen, dann
braucht man auch kein «--» mehr, um sie von den vorausgehenden
Optionen unterscheiden zu können.

Die Forderung, die «find» an die Pfade stellt, hat aber die etwas
lästige Folge, dass man beispielsweise in einem Shell‐Skript, das
einen «find»‐Aufruf machen will und die Pfad‐Parameter nicht selbst
festlegt, sondern sie sich beispielsweise vom Aufrufer in den
positional parameters («"$@"») geben lässt, zuerst alle
Pfad‐Parameter prüfen und die, die mit einem verbotenen Zeichen
beginnen, reparieren muss, etwa:


Statt einfach «find» mit ungeprüften Pfaden aufzurufen


find "$@" …


muss man dann alle Pfade durchgehen und die, die mit «-» oder «(»
beginnen, vorne dran mit «./» versehen.

Ich bin noch etwas vorsichtiger, als nur «-» und «(» zu vermeiden,
sondern repariere gleich alles, was mit einem anderen Zeichen als
«/» oder «.» beginnt, denn es sind mir auch schon Programme über den
Weg gelaufen, bei denen beispielsweise «https:» auch ein verbotener
Pfadanfang ist.  Auf diese Weise sind die Pfade nicht nur für «find»
sondern auch noch für anderweitige Zwecke robust:


for pfad
do
case "$pfad" in
([!/.]*)
# Alles, was mit einem anderen Zeichen als
# '/' oder '.' beginnt, wird repariert:
#
pfad=./"$pfad"
;;
# In allen anderen Faellen ist nichts zu tun.
# Das schliesst auch den Fall, dass ein Pfad
# leer ('', ein ungueltiger Pfad) ist, ein.
esac
shift
set -- "$@" "$pfad"
done
find "$@" …

Machbar, aber lästig.

Marcel Logen

unread,
Aug 10, 2022, 6:55:42 PM8/10/22
to
Helmut Waitzmann in de.comp.os.unix.shell:

>Ich bin noch etwas vorsichtiger, als nur «-» und «(» zu vermeiden,
>sondern repariere gleich alles, was mit einem anderen Zeichen als
>«/» oder «.» beginnt, denn es sind mir auch schon Programme über den
>Weg gelaufen, bei denen beispielsweise «https:» auch ein verbotener
>Pfadanfang ist.  Auf diese Weise sind die Pfade nicht nur für «find»
>sondern auch noch für anderweitige Zwecke robust:

Damit ich sehe, was da im einzelnen passiert, habe ich einige
printf-Anweisungen in Dein Script eingefügt. (Hier aber wieder
abgeschaltet, damit der Output nicht zu lang wird.)

| user14@n14:~$ cat robust11a.bash
| #!/bin/bash
| for pfad
| do
| case "$pfad" in
| ([!/.]*)
| pfad=./"$pfad"
| ;;
| esac
| # printf '"%s"\n' "$@" "---1"
| shift
| # printf '"%s"\n' "$@" "---2"
| set -- "$@" "$pfad"
| # printf '"%s"\n' "$@" "---3"
| done
| printf '"%s"\n' "$@"

| user14@n14:~$ ./robust11a.bash marcel .logen '/germany' -bad ' godesberg' '( foo' '../bar'
| "./marcel"
| ".logen"
| "/germany"
| "./-bad"
| "./ godesberg"
| "./( foo"
| "../bar"
| user14@n14:~$

Ich bin mir nicht sicher, ob es in Deinem Sinne wäre, auch das
".logen" (und evtl. auch das "../bar") mit einem "./" davor zu
versehen. Dann gäbe es nur Pfade mit "/" oder "./" am Anfang
im Output.

Das sollte mit dem Muster "[!/]*" machbar sein.

| user14@n14:~$ ./robust11a.bash marcel .logen '/germany' -bad ' godesberg' '( foo' '../bar'
| "./marcel"
| "./.logen"
| "/germany"
| "./-bad"
| "./ godesberg"
| "./( foo"
| "./../bar"
| user14@n14:~$

Marcel kjns (675580)
--
╭─────────╮ ╭────╮ ╭──────╮ ╭─────╮ ╭────╮ ╭─╮
╰─╮ ╰──╯ ╰────╮ ╭──╮ ╭──╯ ╭───╯ │ ╭──╯ │ ╰─╯ │
╮ ╭─╯ ╭──────────────╯ ╭─╯ ╰──╯ ╰───────╯ │ ╭──╯ ╭───╯ ╭
╰──╯ ╰────────────────╯ ╰─╯ f513a5╰───────╯

Helmut Waitzmann

unread,
Aug 11, 2022, 10:15:27 AM8/11/22
to
Marcel Logen <33320000...@ybtra.de>:
Ja, sehr sogar.  Man sollte halt jedenfalls vermeiden, dass aus
einem Pfad, der mit «./» beginnt, einer wird, der mit «././»
beginnt, weil sonst jedes Mal, wenn man den Pfad durch die
Robustmachung schiebt – was ja passieren könnte, wenn ein
Shell‐Skript mit einprogrammierter Pfad‐Robustmachung ein anderes
mit einprogrammierter Pfad‐Robustmachung aufruft –, ein weiteres Mal
unnötigerweise «./» vorne dran gehängt wird.  Und man sollte auch
vermeiden, dass aus dem Pfad «.» der Pfad «./.» gemacht wird.

>Das sollte mit dem Muster "[!/]*" machbar sein.
>

Das Muster passt auf alle relativen Pfade, auch auf «.» und «./». 
Das scheint mir zu wenig einschränkend zu sein.


Andererseits wollte ich nicht so viel Gehirnschmalz investieren
(denkfaule Sau!).  Deshalb habe ich darauf verzichtet, Pfade, die
mit einem «.», dem kein «/» folgt, beginnen, zu verändern.  Wenn man
das aber auch will – wofür auch einiges spricht, beispielsweise das
Programm «tar» –, kann man so vorgehen (Danke für dein motivierendes
Nachfragen, das dazu geführt hat, bis zum Ende zu denken!):


Dazu noch eine Definition und eine Beobachtung:


‚Komponenten‘ eines Pfades nenne ich die Teile zwischen den «/» im
Pfad, das Anfangsstück des Pfades bis vor den ersten «/» und das
Ende des Pfads nach dem letzten «/».  So eine Komponente –
insbesondere die erste oder die letzte – kann auch leer («») sein. 
(Wenn der Pfad keinen «/» enthält, besteht er aus genau einer
Komponente.)


Beispiele für Komponenten in Pfaden:


«eins» besteht aus einer Komponente: «eins».

«eins/zwei» besteht aus zwei Komponenten: «eins» und «zwei».

«eins/» besteht aus zwei Komponenten: «eins» und «».

«/zwei» besteht aus zwei Komponenten: «» und «zwei».

«» besteht aus einer Komponente: «».

«/» besteht aus zwei Komponenten: «» und «».


Robust im Sinn von oben sind die Pfade, die mit «./» oder «/»
beginnen, sowie der Pfad «.» und der (ungültige) Pfad «».  Anders
ausgedrückt:  Robust sind genau die Pfade, deren erste Komponente
«.» oder «» ist.

Die Variablen‐Expansion «"${pfad%%/*}"» liefert die Zeichenfolge,
die man erhielte, wenn man vom Inhalt der Variablen «pfad» von
rechts beginnend das längste Stück abschneiden würde, das auf das
Muster «/*» passt.

Auf «/*» passt jede Zeichenkette, die mit einem «/» beginnt, dem
danach null oder mehr beliebige Zeichen – wozu auch «/» und «.»
gehören – folgen.

=> «"${pfad%%/*}"» liefert die erste Komponente von «"$pfad"».  Das
ist genau das, was man im folgenden braucht, um zu bestimmen, ob ein
Pfad robust ist oder nicht.

=> Ein Pfad ist genau dann robust, wenn «"${pfad%%/*}"» = «.» oder =
«» ist.  Und das nutze ich im folgenden aus:

for pfad
do
case "${pfad%%/*}" in
(.|'')
# Alles, was mit der Komponente '.' oder '' beginnt,
# ist schon robust und kann bleiben, wie es ist:
#
# Meines Wissens muss in jedem "case"-Zweig
# wenigstens ein Kommando stehen, also nehme ich
# hier das Kommando ":", das nichts tut:
# (Falls ich mich irre, kann man es auch weglassen.)
:
;;
(*)
# Alles andere wird robust gemacht durch Voranstellen
# von './':
#
pfad=./"$pfad"
;;
esac
shift
set -- "$@" "$pfad"
done

Das wäre dann die strengste Robustheit.


Nochmal zitiert:


>Damit ich sehe, was da im einzelnen passiert, habe ich einige
>printf-Anweisungen in Dein Script eingefügt. (Hier aber wieder
>abgeschaltet, damit der Output nicht zu lang wird.)
>
>| # printf '"%s"\n' "$@" "---1"
>| shift
>| # printf '"%s"\n' "$@" "---2"
>| set -- "$@" "$pfad"
>| # printf '"%s"\n' "$@" "---3"

Du lässt innerhalb der Schleife dreimal die positional parameters
anzeigen.  Das lässt mich vermuten, dass zur Wirkungsweise der
«for»‐Schleife vielleicht noch eine Erläuterung angebracht ist:


Zunächst:

for pfad

ist genau dasselbe wie


for pfad in "$@"

nur eine kürzere Schreibweise.  Die Schleifensteuerung merkt sich
die Liste der nacheinander der Variablen «pfad» zuzuweisenden Werte,
und diese gemerkte Liste wird selbst dann nicht mehr verändert, wenn
man im Schleifenrumpf an den positional parameters («"$@"») mit
«set» oder «shift» Veränderungen vornimmt.

Damit erhält die Variable «pfad» nacheinander jeden Wert der
positional parameters, so, wie sie beim Schleifenstart vorgelegen
haben.  Der Witz am Schleifenrumpfteil

shift
set -- "$@" "$pfad"

ist, dass das augenblicklich erste Listenelement vorne von der Liste
der positional parameters weggenommen

shift


und hinten dafür der Inhalt der Variablen «path» drangehängt


set -- "$@" "$pfad"

wird.  Die Liste der postional parameters wird also im Lauf
der Schleife nicht aufgebraucht sondern behält ihre Anzahl
bei.

Trotzdem erhält die Variable «pfad» im weiteren Lauf der
Schleife nacheinander die positional parameters so, wie sie
beim Schleifenstart waren, weil die Auswertung der
positional parameters bei

for pfad in "$@"

nur einmal beim Schleifenstart passiert.  Dass die Liste der
positional parameters im Schleifenrumpf dann wegen «shift» und «set»
verändert wird, kümmert die Funktion der Schleife, alle Elemente der
Liste der positional parameters, so, wie sie beim Schleifenstart
vorgelegen haben, genau ein Mal in die Variable «pfad» zu stecken,
nicht mehr.


Klarer wäre also eigentlich eine Formulierung der Art


for pfad in "$@"
# Loesche die positional parameters, weil sie ja
# sowieso neu aufgebaut werden sollen:
set --
do

# Sammle die rubusten Pfade ein, indem sie hinten an die
# Liste der bereits eingesammelten robusten Pfade
# angehaengt werden:
#
set -- "$@" "$pfad"
done

Aber das kann man so nicht formulieren:  Zwischen dem «for pfad» und
dem «do» kann man kein Kommando einfügen.

Deshalb bleibt nichts anderes übrig, als den alten jetzt eigentlich
wertlosen Inhalt der Liste der positional parameters während des
Schleifendurchlaufs Schritt um Schritt mittels «shift» einzeln zu
löschen.  Ausgenutzt wird dabei, dass

for pfad

die Liste von vorne bis hinten durchgeht, und, dass


shift

glücklicherweise genau das vorderste Element der Liste entfernt.


Dass es sich beim vordersten Element der Liste immer um das handelt,
das von der Schleifensteuerung gerade in die Variable «pfad»
gestellt worden ist, kann man durch folgende Überlegung erkennen:

Beim ersten Schleifendurchlauf ist «"$pfad"» = «"$1"», das erste
Element der positional parameters, denn so ist die Bedeutung von

for pfad in "$@"

festgelegt:  Die «for»‐Schleife arbeitet ihre Liste von vorne nach
hinten ab.  Weil im Schleifenrumpf

shift
set -- "$@" "$pfad"

passiert, wird das erste Element der Liste entfernt und (je nach
Inhalt von «"$pfad"» aufgrund der «case … esac»‐Anweisung
unverändert oder verändert) hinten als robuster Pfad wieder
drangehängt.  Damit ist jetzt das ursprünglich zweite Element der
Liste vorne dran, also als «"$1"» verfügbar.

=> Auch im zweiten Schleifendurchlauf ist «"$pfad"» = «"$1"».


Und genau das wird im zweiten Schleifendurchgang ebenfalls vorne
weggenommen und hinten drangehängt.  Und so wird auch mit allen
weiteren Listenelementen verfahren.

=> Zu Beginn eines jeden Schleifendurchgangs ist genau das Element,
das in der Variablen «pfad» steht, vorne an der Liste dran.  Es wird
vorne weggenommen und hinten in robuster Variante angefügt.

=> Die Liste enthält nach jedem Schleifendurchgang vorne ein
unbearbeitetes Element weniger und hinten ein robustes Element mehr
als zuvor.

=> Die robusten Listenelemente werden in derselben Reihenfolge an
die Liste angefügt, wie die (möglicherweise noch nicht robusten)
Originalelemente von der Liste abgenommen werden.

=> Bei jedem Schleifendurchgang bleibt die Anzahl der Listenelemente
gleich.

=> Wenn die Schleife fertig ist, war jedes Listenelement der
Originalliste genau ein Mal an der Reihe.  => Alle Listenelemente
der positional parameters «"$@"» sind robust und haben ihre
Reihenfolge beibehalten.


Jetzt haben wir so viel Gehirnschmalz reingesteckt.  Da wäre
es doch schön, man könnte sich eine Shell‐Funktion oder ein
Shell‐Skript schreiben, das man irgendwo an festgelegtem
Platz im HOME‐Verzeichnis aufhebt und zukünftig nur noch
aufzurufen braucht, wenn man mal wieder robuste Pfade
braucht.

Und das kann man auch, ist aber einen eigenen Artikel wert.

Helmut Waitzmann

unread,
Aug 11, 2022, 12:31:19 PM8/11/22
to
Helmut Waitzmann <nn.th...@xoxy.net>:

> for pfad
> do
> case "${pfad%%/*}" in
> (.|'')
> # Alles, was mit der Komponente '.' oder '' beginnt,
> # ist schon robust und kann bleiben, wie es ist:
> #
> # Meines Wissens muss in jedem "case"-Zweig
> # wenigstens ein Kommando stehen, also nehme ich
> # hier das Kommando ":", das nichts tut:
> # (Falls ich mich irre, kann man es auch weglassen.)
> :
> ;;
> (*)
> # Alles andere wird robust gemacht durch Voranstellen
> # von './':
> #
> pfad=./"$pfad"
> ;;
> esac
> shift
> set -- "$@" "$pfad"
> done

[…]

>Jetzt haben wir so viel Gehirnschmalz reingesteckt.  Da wäre es doch
>schön, man könnte sich eine Shell‐Funktion oder ein Shell‐Skript
>schreiben, das man irgendwo an festgelegtem Platz im HOME‐Verzeichnis
>aufhebt und zukünftig nur noch aufzurufen braucht, wenn man mal wieder
>robuste Pfade braucht.
>
>Und das kann man auch, ist aber einen eigenen Artikel wert.
>

So eine Funktion bekäme eine Liste von Pfaden übergeben und könnte
sie in Form der bearbeiteten positional parameters zurückgeben.

Dabei gibt es allerdings ein Problem:  Die positional parameters
sind innerhalb einer Funktion automatisch lokal, das heißt, nach
Beendigung der Funktion sehen sie wieder so aus wie vor dem
Funktionsaufruf. => Die positional parameters taugen nicht zur
Funktionswert‐Rückgabe.

Aber im «bash» gibt es Feldvariablen, und die werden nicht
automatisch lokal.

Am besten ist es dabei, der Aufrufer der Funktion kann den Namen der
Feldvariablen, in der die robust gemachte Pfadeliste zurückgegeben
wird, wählen (so, wie man ja auch beim Kommando «read» den Namen der
Variablen angeben kann).

Man könnte der Funktion diesen Namen als ersten Parameter übergeben
und in den weiteren Parametern die Pfade.

Ein weiterer zu beachtender Punkt für eine brauchbare Funktion ist
noch folgender:  Die Funktion darf keine weiteren globalen Variablen
ändern (also Seiteneffekte verursachen), beispielsweise auch nicht
die in der «for»‐Schleife verwendete Variable «pfad».

Aber auch das ist mit dem «bash» hinzubekommen, weil es dort die
Möglichkeit gibt, funktionslokale Variable zu definieren (mit dem
Kommando «local»).

Also könnte die «bash»‐Funktion so aussehen (ich gebe ihr einen
Namen, der mit «my_» beginnt, in der Hoffnung, dass weder der
Distributor noch der Systemadministrator solche Namen verwendet
sondern diesen Namensraum den einzelnen Anwendern überlässt):

my_robuste_pfade()
{
# Der erste Parameter ist der Name der Feldvariablen, in
# der das Ergebnis zurueckgegeben werden wird.
#
# Weitere Parameter sind die Pfade, die robust gemacht
# werden sollen.

# Verwendungsbeispiel:
#
# Das Shell-Kommando
#
# my_robuste_pfade ergebnis …
#
# liefert in der Feldvariablen 'ergebnis' die robusten
# Varianten der Aufrufparameter.

local -- name_der_ergebnisvariablen pfad &&
name_der_ergebnisvariablen="${1:?}" &&
# Der Name der Ergebnisvariablen stoert im Weiteren in
# der Parameterliste nur, deshalb wird er entfernt:
#
shift &&
declare -a -- "$name_der_ergebnisvariablen" &&

for pfad
do
case "${pfad%%/*}" in
(.|'')
# Alles, was mit der Komponente '.' oder '' beginnt,
# ist schon robust und kann bleiben, wie es ist:
#
# Meines Wissens muss in jedem "case"-Zweig
# wenigstens ein Kommando stehen, also nehme ich
# hier das Kommando ":", das nichts tut:
# (Falls ich mich irre, kann man es auch weglassen.)
:
;;
(*)
# Alles andere wird robust gemacht durch Voranstellen
# von './':
#
pfad=./"$pfad"
;;
esac &&
shift &&
set -- "$@" "$pfad"
done &&
eval "$name_der_ergebnisvariablen"'=("$@")'
}


Man kann diese Funktion beispielsweise in die Datei
«~/my_shell-libs/my_robuste_pfade.bash» stellen.  Dann genügt es,
den «bash» die Datei mittels des Kommandos

. ~/my_shell-libs/my_robuste_pfade.bash

lesen zu lassen.  Dann kennt er die Funktion «my_robuste_pfade», und
man kann sie beispielsweise so verwenden:

my_robuste_pfade ergebnis '' '/' '.' './' '..' '../' '.a' '.a/'



Zum in der Funktion verwendeten Kommando


eval "$name_der_ergebnisvariablen"'=("$@")'


ist vielleicht auch noch eine Erklärung sinnvoll:


«eval» nimmt seine Parameter entgegen, klebt sie (wenn es mehr als
ein Parameter ist) mit Leerstellen aneinander und erhält dadurch
eine (möglicherweise lange) Zeichenkette.  Die Zeichenkette nimmt es
als Kommandozeile her, so, als würde sie im Shell‐Skript stehen, und
führt sie aus.  Am konkreten Beispiel ist das vielleicht leichter zu
verstehen:  Mit der Kommandozeile

eval "$name_der_ergebnisvariablen"'=("$@")'

erhält «eval» genau einen Parameter.  Der Parameter beginnt mit dem
Namen der Ergebnisvariablen (denn der ist der Inhalt der Variablen
«name_der_ergebnisvariablen», dann folgen – denn der Rest ist von
«'» eingefasst – die folgenden Zeichen:

«=("$@")»

Wenn man die Funktion also so aufgerufen hat:


my_robuste_pfade ergebnis '' '/' '.' './' '..' '../' '.a' '.a/'

erhält «eval» den folgenden Parameter:


«ergebnis=("$@")»

Das ist, als Kommandozeile verstanden, eine Zuweisung der positional
parameters an die Feldvariable «ergebnis».  In den positional
parameters stehen die robusten Entsprechungen der Aufrufparameter
der Funktion, und der Effekt ist, dass die Feldvariable «ergebnis»
danach das gewünschte Ergebnis enthält.

«eval», unvorsichtig verwendet, kann leicht zu Exploits führen.  Das
ist hier jedoch nicht der Fall, weil der in «'» eingefasste Teil
genau so gefährlich ist, als stünde er direkt ohne «eval» in einer
Kommandozeile, etwa:

feldvariable=("$@")


Der einzige Teil, der auf den ersten Blick gefährlich werden könnte,
ist der Variableninhalt der Variablen «name_der_ergebnisvariablen».

Dass es sich bei dem Variableninhalt aber tatsächlich um den Namen
einer Feldvariablen handelt, ist durch das Kommando

declare -a -- "$name_der_ergebnisvariablen"

weiter vorne in der Funktion sichergestellt, denn das funktioniert
nicht, wenn es als Variablennamen Zeichenfolgen erhält, die nicht
als Variablennamen erlaubt sind.  Wichtig sind dabei die
Anführungszeichen, damit der Variablenname, sollte er beispielsweise
«-p HOME» sein, nicht in die Teile «-p» und «HOME» zerbricht, und
das Optionenende «--», damit nicht etwa der Variablenname «-p» als
Option missverstanden werden kann.


Weise wäre es, die Funktion gleich von vorne herein so zu entwerfen,
dass sie ihrerseits das Optionenende «--» nutzt, denn dann hat man
die Möglichkeit, beispielsweise die Option «-h», um Hilfe zur
Verwendung auszugeben, zu implementieren oder noch zukünftige
Erweiterungen (wer weiß, was einem da noch einfallen könnte) zu
ermöglichen.  Aber das kostet schnell mal 50 Zeilen Code, und da
wehrt sich noch die faule Sau in mir.


Diese Funktion so zu schreiben, dass sie nur mit den Mitteln
arbeitet, die der POSIX‐Standard bereithält, ist, wenn man in Kauf
nimmt, dass man sie nicht auf die Weise


my_robuste_pfade ergebnis …

aufrufen kann, sondern beispielsweise auf die Weise


eval "$( my_robuste_pfade ergebnis … )"

aufrufen muss, möglich, aber um einiges aufwendiger, und einen
eigenen News‐Artikel wert.

Helmut Waitzmann

unread,
Aug 12, 2022, 10:32:00 AM8/12/22
to
Helmut Waitzmann <nn.th...@xoxy.net>:
>Helmut Waitzmann <nn.th...@xoxy.net>:

[…]

>> Da wäre es doch schön, man könnte sich eine Shell‐Funktion oder
>> ein Shell‐Skript schreiben, das man irgendwo an festgelegtem
>> Platz im HOME‐Verzeichnis aufhebt und zukünftig nur noch
>> aufzurufen braucht, wenn man mal wieder robuste Pfade braucht.
>>
>> Und das kann man auch, ist aber einen eigenen Artikel wert.
>>
>
>So eine Funktion bekäme eine Liste von Pfaden übergeben und könnte sie
>in Form der bearbeiteten positional parameters zurückgeben.
>
>Dabei gibt es allerdings ein Problem:  Die positional parameters sind
>innerhalb einer Funktion automatisch lokal, das heißt, nach Beendigung
>der Funktion sehen sie wieder so aus wie vor dem Funktionsaufruf. =>
>Die positional parameters taugen nicht zur Funktionswert‐Rückgabe.
>
>Aber im «bash» gibt es Feldvariablen, und die werden nicht automatisch
>lokal.
>
>Am besten ist es dabei, der Aufrufer der Funktion kann den Namen der
>Feldvariablen, in der die robust gemachte Pfadeliste zurückgegeben
>wird, wählen (so, wie man ja auch beim Kommando «read» den Namen der
>Variablen angeben kann).
>
>Man könnte der Funktion diesen Namen als ersten Parameter übergeben
>und in den weiteren Parametern die Pfade.

[…]
Leider funktioniert das nicht, wie gedacht, wenn man beim Aufruf
einen Variablennamen angibt, der in der Funktion als lokale Variable
verwendet wird, beispielsweise den Namen «pfad».

Aber bei einem hinreichend neuen «bash» gibt es die Option «-g» beim
«declare»‐Kommando, und mit dem kann man den globalen Wert einer
Variablen, die von einer lokalen Variablen gleichen Namens verdeckt
wird, ändern.  Damit kann man die Funktion jetzt so fassen:

my_robuste_pfade()
{
# Aufruf:
#
# my_robuste_pfade ergebnisvar Pfade...
#
# Der erste Parameter ist der Name der Feldvariablen, in
# der das Ergebnis zurueckgegeben werden wird.
#
# Weitere Parameter sind die Pfade, die robust gemacht
# werden sollen.

# Verwendungsbeispiel:
#
# Das Shell-Kommando
#
# my_robuste_pfade ergebnis …
#
# liefert in der Feldvariablen 'ergebnis' die robusten
# Varianten der Aufrufparameter.

local -- - &&
set +x -e -u &&

local -- ergebnisvarname pfad &&
ergebnisvarname="${1?"${FUNCNAME[0]}"': Der Name der Ergebnisvariablen fehlt.'}" &&

# Der Name der Ergebnisvariablen stoert im Weiteren in
# der Parameterliste nur, deshalb wird er entfernt:
#
shift &&
if ! LC_ALL=C expr " $ergebnisvarname" : \
' [[:alpha:]_][[:alnum:]_]*$' > /dev/null
then
printf >&2 '%s: %q ist als Variablenname nicht erlaubt.\n' \
"${FUNCNAME[0]}" "$ergebnisvarname"
return 1
fi &&
for pfad
do
case "${pfad%%/*}" in
(.|'')
# Alles, was mit der Komponente '.' oder '' beginnt,
# ist schon robust und kann bleiben, wie es ist:
#
# Meines Wissens muss in jedem "case"-Zweig
# wenigstens ein Kommando stehen, also nehme ich
# hier das Kommando ":", das nichts tut:
# (Falls ich mich irre, kann man es auch weglassen.)
:
;;
(*)
# Alles andere wird robust gemacht durch Voranstellen
# von './':
#
pfad=./"$pfad"
;;
esac &&
shift &&
set -- "$@" "$pfad"
done &&
eval 'declare -g -a -- '"$ergebnisvarname"'=("$@")'

Marcel Logen

unread,
Aug 18, 2022, 7:41:49 PM8/18/22
to
Helmut Waitzmann in de.comp.os.unix.shell:

>Marcel Logen <33320000...@ybtra.de>:

[...]

>>Damit ich sehe, was da im einzelnen passiert, habe ich einige
>>printf-Anweisungen in Dein Script eingefügt. (Hier aber wieder
>>abgeschaltet, damit der Output nicht zu lang wird.)
>>
>>| # printf '"%s"\n' "$@" "---1"
>>| shift
>>| # printf '"%s"\n' "$@" "---2"
>>| set -- "$@" "$pfad"
>>| # printf '"%s"\n' "$@" "---3"
>
>Du lässt innerhalb der Schleife dreimal die positional parameters
>anzeigen.

Damit kann ich schön sehen, wie shift und set hier arbeiten.

> Das lässt mich vermuten, dass zur Wirkungsweise der
>«for»‐Schleife vielleicht noch eine Erläuterung angebracht ist:

Danke.

>Zunächst:
>
> for pfad
>
>ist genau dasselbe wie
>
> for pfad in "$@"
>
>nur eine kürzere Schreibweise.

Ja, das hatte ich schon herausgefunden.

>   Die Schleifensteuerung merkt sich die
>Liste der nacheinander der Variablen «pfad» zuzuweisenden Werte, und
>diese gemerkte Liste wird selbst dann nicht mehr verändert, wenn man im
>Schleifenrumpf an den positional parameters («"$@"») mit «set» oder
>«shift» Veränderungen vornimmt.

Genau da hatte ich eventuelles 'Durcheinander' vermutet, deshalb
meine zusätzlichen Anweisungen fürs 'Debugging'.

>Damit erhält die Variable «pfad» nacheinander jeden Wert der positional
>parameters, so, wie sie beim Schleifenstart vorgelegen haben.

[...]

>Trotzdem erhält die Variable «pfad» im weiteren Lauf der Schleife
>nacheinander die positional parameters so, wie sie beim Schleifenstart
>waren, weil die Auswertung der positional parameters bei
>
> for pfad in "$@"
>
>nur einmal beim Schleifenstart passiert.

[...]

>=> Wenn die Schleife fertig ist, war jedes Listenelement der
>Originalliste genau ein Mal an der Reihe.  => Alle Listenelemente der
>positional parameters «"$@"» sind robust und haben ihre Reihenfolge
>beibehalten.

Ja. Auf Einzelheiten Deines Codes gehe ich zu einem späteren
Zeitpunkt noch ein.

Marcel 2l2c (87116)
--
╭────╮ ╭──╮ ╭─────╮
╰──╮ ╰─╯ ╰──╮ ╰──╮ ╰──╮
╭─╯ ╭──────╯ ╭──────╯ ╭──╯ ╭───╮ ╭─────╮ ╭────╮ ╭──╮
──╯ ╰──────────╯ dce789 ╰──────╯ ╰─╯ ╰───╯ ╰───╯ ╰────────

Marcel Logen

unread,
Aug 29, 2022, 7:34:56 PM8/29/22
to
Helmut Waitzmann in de.comp.os.unix.shell:

>Helmut Waitzmann <nn.th...@xoxy.net>:

[...]

>>Zum in der Funktion verwendeten Kommando
>>
>> eval "$name_der_ergebnisvariablen"'=("$@")'
>>
>>ist vielleicht auch noch eine Erklärung sinnvoll:
>>
>>«eval» nimmt seine Parameter entgegen, klebt sie (wenn es mehr als ein
>>Parameter ist) mit Leerstellen aneinander und erhält dadurch eine
>>(möglicherweise lange) Zeichenkette.  Die Zeichenkette nimmt es als
>>Kommandozeile her, so, als würde sie im Shell‐Skript stehen, und führt
>>sie aus.  Am konkreten Beispiel ist das vielleicht leichter zu
>>verstehen:  Mit der Kommandozeile

Den Sinn von "eval" habe ich leider noch nicht verstanden.

Warum muß man die Zeichenkette/Kommandozeile mit "eval" ein-
leiten? Kann man sie nicht einfach direkt hinschreiben?

[...]

>wird, ändern.  Damit kann man die Funktion jetzt so fassen:
>
> my_robuste_pfade()
> {
> # Aufruf:
> #
> # my_robuste_pfade ergebnisvar Pfade...
> #
> # Der erste Parameter ist der Name der Feldvariablen, in
> # der das Ergebnis zurueckgegeben werden wird.
> #
> # Weitere Parameter sind die Pfade, die robust gemacht
> # werden sollen.
>
> # Verwendungsbeispiel:
> #
> # Das Shell-Kommando
> #
> # my_robuste_pfade ergebnis …
> #
> # liefert in der Feldvariablen 'ergebnis' die robusten
> # Varianten der Aufrufparameter.
>
> local -- - &&

Hier werden die "shell options" funktions-lokal gemacht, ...

> set +x -e -u &&

... z. B. wird das Shell-Attribut "x" (command expansion) abgeschal-
tet, "e" (stop on error) und "u" (expansion of unset variables) ein-
geschaltet. (Die Bezeichnugen in den Klammern stammen von mir.)

> local -- ergebnisvarname pfad &&
> ergebnisvarname="${1?"${FUNCNAME[0]}"': Der Name der Ergebnisvariablen fehlt.'}" &&
>
> # Der Name der Ergebnisvariablen stoert im Weiteren in
> # der Parameterliste nur, deshalb wird er entfernt:
> #
> shift &&
> if ! LC_ALL=C expr " $ergebnisvarname" : \
> ' [[:alpha:]_][[:alnum:]_]*$' > /dev/null

Hier wird offenbar geprüft, ob der Variablenname aus den erlaubten
Zeichen besteht. Wenn nicht, erscheint eine Fehlermeldung:

> then
> printf >&2 '%s: %q ist als Variablenname nicht erlaubt.\n' \
> "${FUNCNAME[0]}" "$ergebnisvarname"

Der Text wird auf stderr ausgegeben, wenn ich das richtig sehe.

> return 1
> fi &&
> for pfad
> do
> case "${pfad%%/*}" in
> (.|'')

Hier, dachte ich zunächst, könnte man auch "(.|)" schreiben, aber
das klappt so nicht:

| user14@n14:~$ case '' in (.|'') echo eins ;; (*) echo zwei ;; esac
| eins

| user14@n14:~$ case '' in (.|) echo eins ;; (*) echo zwei ;; esac
| bash: Syntaxfehler beim unerwarteten Symbol »)«

| user14@n14:~$ case '.' in (.|'') echo eins ;; (*) echo zwei ;; esac
| eins

| user14@n14:~$ case '.bar' in (.|'') echo eins ;; (*) echo zwei ;; esac
| zwei

> # Alles, was mit der Komponente '.' oder '' beginnt,
> # ist schon robust und kann bleiben, wie es ist:
> #
> # Meines Wissens muss in jedem "case"-Zweig
> # wenigstens ein Kommando stehen, also nehme ich
> # hier das Kommando ":", das nichts tut:
> # (Falls ich mich irre, kann man es auch weglassen.)
> :
> ;;
> (*)
> # Alles andere wird robust gemacht durch Voranstellen
> # von './':
> #
> pfad=./"$pfad"
> ;;
> esac &&
> shift &&
> set -- "$@" "$pfad"
> done &&
> eval 'declare -g -a -- '"$ergebnisvarname"'=("$@")'
> }

Zu "eval" siehe meine Frage oben.

Ich habe Deinen Code in eine Datei kopiert und diese dann eingelesen:

| user14@n14:~$ source ./robust19a.bash.source

| user14@n14:~$ my_
| my_print_defaults my_robuste_pfade

Hinter dem "my_" habe ich die Tabulator-Taste gedrückt.

Man sieht, daß wohl im System schon ein Kommando vorhanden ist, das
mit "my_" beginnt.

| user14@n14:~$ my_robuste_pfade ev '/' '' '.' './foo' 'bar' ' ' '.baz' './.qux' '/.quux'

| user14@n14:~$ declare -p ev
| declare -a ev=([0]="/" [1]="" [2]="." [3]="./foo" [4]="./bar" [5]="./ " [6]="./.baz" [7]="./.qux" [8]="/.quux")

Scheint also so zu funktionieren wie gedacht. Danke!

Marcel jcjq (635514)
--
╭─╮ ╭─────╮ ╭──────╮ ╭──────╮ ╭─╮ ╭─────╮ ╭─╮ ╭─╮
╯ ╰─╯ ╰─╮ ╰────╮ ╰──╮ ╰─╮ ╰──╯ │ ╰───╮ ╰─╯ │ │ ╰───────
╭────────╯ │ ╰─╮ ╭─╮ ╰─────╮ ╰────╮ ╰─╮ ╰─╯
╰──────────────────╯ ╰─╯ ╰────────╯ ╰────╯ 5727e0

Marcel Logen

unread,
Aug 29, 2022, 8:50:45 PM8/29/22
to
Marcel Logen in de.comp.os.unix.shell:

>Helmut Waitzmann in de.comp.os.unix.shell:
>>Helmut Waitzmann <nn.th...@xoxy.net>:

>>>Zum in der Funktion verwendeten Kommando
>>>
>>> eval "$name_der_ergebnisvariablen"'=("$@")'
>>>
>>>ist vielleicht auch noch eine Erklärung sinnvoll:
>>>
>>>«eval» nimmt seine Parameter entgegen, klebt sie (wenn es mehr als ein
>>>Parameter ist) mit Leerstellen aneinander und erhält dadurch eine
>>>(möglicherweise lange) Zeichenkette.  Die Zeichenkette nimmt es als
>>>Kommandozeile her, so, als würde sie im Shell‐Skript stehen, und führt
>>>sie aus.  Am konkreten Beispiel ist das vielleicht leichter zu
>>>verstehen:  Mit der Kommandozeile
>
>Den Sinn von "eval" habe ich leider noch nicht verstanden.
>
>Warum muß man die Zeichenkette/Kommandozeile mit "eval" ein-
>leiten? Kann man sie nicht einfach direkt hinschreiben?

Kommando zurück! :-)

Kaum habe ich es hingeschrieben, dämmert es mir.

| user14@n14:~$ kkk=lll

| user14@n14:~$ echo "$kkk"
| lll

| user14@n14:~$ "$kkk"=mmm
| bash: lll=mmm: command not found

| user14@n14:~$ eval "$kkk"=mmm

| user14@n14:~$ declare -p kkk lll
| declare -- kkk="lll"
| declare -- lll="mmm"

Die mit "eval" eingeleitete Kommandozeile wird also - in gewisser
Weise - "evaluiert", wie der Name schon sagt.

Ohne und mit "eval":

| user14@n14:~$ nnn=(1 3 5 7)

| user14@n14:~$ declare -p nnn
| declare -a nnn=([0]="1" [1]="3" [2]="5" [3]="7")

| user14@n14:~$ ooo=nnn

| user14@n14:~$ declare -p ooo
| declare -- ooo="nnn"

| user14@n14:~$ "$ooo"=(2 4 6 8)
| bash: syntax error near unexpected token `2'

| user14@n14:~$ declare -p ooo
| declare -- ooo="nnn"

| user14@n14:~$ eval "$ooo"=(2 4 6 8)
| bash: syntax error near unexpected token `('

| user14@n14:~$ declare -p ooo
| declare -- ooo="nnn"

| user14@n14:~$ eval "$ooo"='(2 4 6 8)'

| user14@n14:~$ declare -p ooo
| declare -- ooo="nnn"

| user14@n14:~$ declare -p nnn
| declare -a nnn=([0]="2" [1]="4" [2]="6" [3]="8")

| user14@n14:~$ declare -p "$ooo"
| declare -a nnn=([0]="2" [1]="4" [2]="6" [3]="8")

| user14@n14:~$ "$ooo"='(2 4 6 8)'
| bash: nnn=(2 4 6 8): command not found

Jetzt wird es mir klar(er).

Marcel jek4 (637572)
--
╭─╮ ╭──────╮ ╭────╮ ╭──────╮ ╭──
╯ ╰─╮ ╰─╮ ╭─╯ ╰─╮ ╰──╯ ╰──╮ ╭─╮ ╭─╮ ╭────╯
╰───╮ │ ╰─╮ ╭─╮ ╭────╯ │ ╭─╯ │ │ ╰──╮ │
╰─╯ ╰──╯ ╰───╯ ╰─╯ ╰────╯ ╰──╯ b543c4

Helmut Waitzmann

unread,
Aug 30, 2022, 1:56:19 PM8/30/22
to
Marcel Logen <33320000...@ybtra.de>:
Der Witz an der Kommandozeile im Shell ist, dass die Verarbeitung
aus mindestens 3 Schritten besteht:

Zuerst wird die Kommandozeile syntaktisch untersucht (siehe
<https://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18>): 
Runde Klammern, Scheifklammern, Strichpunkte, «&»‐ und
«|»‐Zeichen und andere syntaktische Elemente der
Programmier‐Sprache «Shell», beispielsweise auch Schlüsselwörter
wie «if», «then», «fi», «for», «while», «case»…,
Variablenzuweisungen, Umlenkoperatoren… und schließlich auch
simple commands werden erkannt.  Dann werden command substitution
(«$(…)» und «`…`»), Variable expandiert («$variable») und, falls
die Expansion nicht in Anführungszeichen («"$variable"») sondern
ohne dasteht («$variable»), das Ergebnis der Expansion an allen
darin enthaltenen Zeichen, die im Wert der Variablen «IFS»
enthalten sind, zersägt, Dateinamensmuster werden expandiert… 
Schließlich wird der Name des zu startenden simple commands und
seine Parameter ermittelt, Ein‐ und Ausgabeumlenkungen
vorgenommen und schließlich das Kommando gestartet.


Bei der Kommandozeile


"$name_der_ergebnisvariablen"=…

kommt die Expansion der Variablen erst, wenn es für die Erkennung
einer Zuweisung bereits zu spät ist.  Erkannt wird dann ein
simple command, dessen Programmname beispielsweite mit

«ergebnis=»

beginnt.  Das ist dann aber keine Variablenzuweisung mehr.


Bei der Kommandozeile


eval "$name_der_ergebnisvariablen"=…

ist das anders:  Das ist (zunächst) keine Zuweisung sondern
tatsächlich nur ein simple command: «eval».  Bevor «eval»
schließlich startet, geschieht die Variablenexpansion.  Wenn die
Variable «name_der_ergebnisvariablen» beispielsweise den Wert
«ergebnis» hat, bekommt «eval» folgenden Text übergeben:

eval ergebnis=…

Weil «eval» den Kommandozeilenparser (nochmals) anwirft, sieht
der jetzt «ergebnis=…» und erkennt eine Zuweisung an die Variable
«ergebnis».

[erhellende Beispiele gelöscht]


Helmut Waitzmann

unread,
Aug 30, 2022, 1:56:23 PM8/30/22
to
Marcel Logen <33320000...@ybtra.de>:
> Helmut Waitzmann in de.comp.os.unix.shell:
>
>>Helmut Waitzmann <nn.th...@xoxy.net>:
>
> [...]
>
>> my_robuste_pfade()
>> {
>> # Aufruf:
>> #
>> # my_robuste_pfade ergebnisvar Pfade...
>> #
>> # Der erste Parameter ist der Name der Feldvariablen, in
>> # der das Ergebnis zurueckgegeben werden wird.
>> #
>> # Weitere Parameter sind die Pfade, die robust gemacht
>> # werden sollen.
>>
>> # Verwendungsbeispiel:
>> #
>> # Das Shell-Kommando
>> #
>> # my_robuste_pfade ergebnis …
>> #
>> # liefert in der Feldvariablen 'ergebnis' die robusten
>> # Varianten der Aufrufparameter.
>>
>> local -- - &&
>
> Hier werden die "shell options" funktions-lokal gemacht, ...
>

Ja, genau, denn ich will sie innerhalb der Funktion geändert
haben, aber nach dem Ende sollen sie wieder so sein wie zuvor.

>> set +x -e -u &&
>
> ... z. B. wird das Shell-Attribut "x" (command expansion) abgeschal-
> tet, "e" (stop on error) und "u" (expansion of unset variables) ein-
> geschaltet. (Die Bezeichnugen in den Klammern stammen von mir.)

Genau.  Wenn man die Funktion in einem Shell‐Skript verwendet und
im Shell‐Skript «set -x» einstellt, um ihm zuschauen zu können,
wie es läuft, will man ja im Allgemeinen nicht in diese Funktion
hineinschauen (von der man ja bereits weiß – oder zu wissen
glaubt – dass sie funktioniert).

>
>> local -- ergebnisvarname pfad &&
>> ergebnisvarname="${1?"${FUNCNAME[0]}"': Der Name der Ergebnisvariablen fehlt.'}" &&
>>
>> # Der Name der Ergebnisvariablen stoert im Weiteren in
>> # der Parameterliste nur, deshalb wird er entfernt:
>> #
>> shift &&
>> if ! LC_ALL=C expr " $ergebnisvarname" : \
>> ' [[:alpha:]_][[:alnum:]_]*$' > /dev/null
>
> Hier wird offenbar geprüft, ob der Variablenname aus den erlaubten
> Zeichen besteht. Wenn nicht, erscheint eine Fehlermeldung:

Genau.  Man muss ja verhindern, dass der Aufrufer als
Variablenname irgend etwas Unpassendes übergibt.

>
>> then
>> printf >&2 '%s: %q ist als Variablenname nicht erlaubt.\n' \
>> "${FUNCNAME[0]}" "$ergebnisvarname"
>
> Der Text wird auf stderr ausgegeben, wenn ich das richtig sehe.
>

Ja.  Fehlermeldungen gehören nach stderr.  Der Umlenkoperator
muss dabei bei einem simple command nicht ganz hinten stehen. 
Damit ich sofort sehe, wohin die Ausgabe von «printf» geht, habe
ich ihn gleich vorne hinter «printf» gesetzt.

>> return 1
>> fi &&
>> for pfad
>> do
>> case "${pfad%%/*}" in
>> (.|'')
>
> Hier, dachte ich zunächst, könnte man auch "(.|)" schreiben, aber
> das klappt so nicht:
>
> | user14@n14:~$ case '' in (.|'') echo eins ;; (*) echo zwei ;; esac
> | eins
>
> | user14@n14:~$ case '' in (.|) echo eins ;; (*) echo zwei ;; esac
> | bash: Syntaxfehler beim unerwarteten Symbol »)«
>
> | user14@n14:~$ case '.' in (.|'') echo eins ;; (*) echo zwei ;; esac
> | eins
>
> | user14@n14:~$ case '.bar' in (.|'') echo eins ;; (*) echo zwei ;; esac
> | zwei

Das ist mir ganz genau so gegangen.


>
>> # Alles, was mit der Komponente '.' oder '' beginnt,
>> # ist schon robust und kann bleiben, wie es ist:
>> #
>> # Meines Wissens muss in jedem "case"-Zweig
>> # wenigstens ein Kommando stehen, also nehme ich
>> # hier das Kommando ":", das nichts tut:
>> # (Falls ich mich irre, kann man es auch weglassen.)
>> :
>> ;;
>> (*)
>> # Alles andere wird robust gemacht durch Voranstellen
>> # von './':
>> #
>> pfad=./"$pfad"
>> ;;
>> esac &&
>> shift &&
>> set -- "$@" "$pfad"
>> done &&
>> eval 'declare -g -a -- '"$ergebnisvarname"'=("$@")'
>> }
>
> Zu "eval" siehe meine Frage oben.
>

[…]

> | user14@n14:~$ my_robuste_pfade ev '/' '' '.' './foo' 'bar' ' ' '.baz' './.qux' '/.quux'
>
> | user14@n14:~$ declare -p ev
> | declare -a ev=([0]="/" [1]="" [2]="." [3]="./foo" [4]="./bar" [5]="./ " [6]="./.baz" [7]="./.qux" [8]="/.quux")
>
> Scheint also so zu funktionieren wie gedacht. Danke!
>

Ich hatte ja geschrieben, dass sich die faule Sau in mir noch
dagegen wehrt, die Funktion so umzuarbeiten, dass sie den
Optionen‐Ende‐Parameter «--» und vielleicht Optionen, etwa die
Option «-h» für Hilfe, akzeptiert.  Und die faule Sau hat
gewonnen, aber nicht deswegen, weil sie ihre Faulheit
durchgesetzt hat, sondern weil sie mir bewiesen hat, dass das
Gewünschte nicht machbar ist:

Zwei Probleme hat es noch, die man meines Wissens nicht beheben
kann (es sei denn, man geht ganz anders vor, s.u.):

Wenn man vor dem Aufruf der Funktion beispielsweise die Variable
«pfad», die ja innen drin als lokale Variable verwendet wird,
beispielsweise mit der «readonly»‐Eigenschaft versieht, hat diese
Eigenschaft auch die lokale Variable «pfad», und man hat
keine Möglichkeit, diese Eigenschaft innerhalb der Funktion
loszuwerden.  Dann scheitert die Funktion:

readonly -- pfad
my_robuste_pfade ev / '' '.'

Für die Hilfeausgabe würde ich eine Funktion, etwa «syntax»
genannt, schreiben wollen.  So weit ich weiß, kann man aber keine
lokalen Funktionen, die also nur innerhalb der Funktion
«my_robuste_pfade» existieren, schreiben.  Oder anders
ausgedrückt:  Nach dem erstmaligen Aufrufen der Funktion
«my_robuste_pfade» gäbe es dann die Funktion «syntax», die
außerhalb der Funktion «my_robuste_pfade» keinen Sinn hätte und
eine möglicherweise bereits vorher vorhandene Funktion «syntax»
ersetzen würde.  Also scheitert das auch daran.

Von daher gehe ich dann doch anders vor:  Ich schreibe keine
Funktion «my_robuste_pfade» sondern ein Shell‐Skript
«my_robuste_pfade»:  Das Shell‐Skript wird von einem neuen Shell,
der von den Shell‐Variablen in seiner Aufrufumgebung (außer den
Umgebungsvariablen) nichts mitbekommt, abgearbeitet.  Auch kann
man innerhalb des Shell‐Skripts Funktionen definieren, ohne dass
die nach außen wirken.  => Beide Probleme sind verschwunden.

Allerdings hat man dann ein anderes, das damit zusammenhängt:  Man
kann innerhalb eines Shell‐Skripts keinen Einfluss auf Variable
außerhalb des Shell‐Skripts nehmen.  Also gibt es keine
Möglichkeit, im Shell‐Skript ein «eval»‐Kommando so aufzurufen,
dass es eine Feldvariable außerhalb des Shell‐Skripts mit dem
Ergebnis füllt.

Aber es gibt eine andere Möglichkeit, die man in solchen Fällen
gerne nutzt:  Man kann das Shell‐Skript «my_robuste_pfade» so
schreiben, dass es eine Kommandozeile, die eine
Feldvariablen‐Zuweisung darstellt, als Ausgabe ausspuckt.  Die
nimmt man dann als Aufrufer entgegen und legt sie «eval» zum
Ausführen vor:

kommandozeile="$(
my_robuste_pfade -- ev die Pfade als Parameter
)" &&
eval "$kommandozeile"

Dabei ist es sogar noch besser, wenn man es noch anders macht:
«my_robuste_pfade» soll nicht eine vollständige Kommandozeile
(für die Zuweisung des Ergebnisses an eine Feldvariable)
ausgeben, sondern nur einen Teil einer Kommandozeile, nämlich die
robusten Entsprechungen der übergebenen Pfade.

Der Vorteil daran ist, dass das Shell‐Skript «my_robuste_pfade»
nicht mehr mit einem Variablennamen für das Ergebnis aufgerufen
wird.  Und deshalb muss es sich auch nicht mehr darum kümmern, ob
der Variablenname gültig ist.

Der Variablenname wird ihm nicht übergeben, und es liefert auch
keine Variablenzuweisung sondern nur den Teil einer
Kommandozeile, der die Liste der robusten Pfade darstellt. 
Ob der Aufrufer die Liste dann mit einer Zuweisung an eine
Feldvariable oder mit einem «set»‐Kommando zum Setzen der
positional parameters kombiniert, bleibt dann ihm überlassen.

Man kann das Shell‐Skript beispielsweise so aufrufen:


commandozeilenteil="$(
my_robuste_pfade -- die Pfade als Parameter
)" &&

# und dann die positional parameters mit dem Ergebnis
# belegen:
eval 'set -- '"$kommandozeilenteil"

# oder im Bash eine Feldvariable füllen:
eval 'ergebnis=( '"$kommandozeilenteil"' )'

Ein Shell‐Skript statt einer Funktion zu verwenden, hat dann auch
den Vorteil, dass man das Konzept der funktionslokalen
Shell‐Variablen nicht mehr braucht, und, dass keine störenden
«readonly»‐Attribute von Shell‐Variablen von außen mehr
hereindrücken können.  Auch ist es ohne weiteres möglich,
innerhalb des Shell‐Skripts die benötigte Funktion «syntax» für
die Ausgabe des Hilfetexts zu schreiben, ohne dass die nach außen
entweichen kann.  => Man braucht für das Shell‐Skript nur noch
Fähigkeiten, die jeder zum POSIX‐Standard kompatible Shell
mitbringt.  Und auch die Verwendung kommt damit aus, wenn man mit
der Ausgabe des Shell‐Skripts keine Zuweisung an eine
Feldvariable sondern an die positional parameters zusammensetzt.

Allerdings hat man jetzt noch eine Aufgabe zu lösen:  Wenn man
innerhalb der Funktion die robusten Pfade berechnet hat, muss man
sie so ausgeben, dass sie Teil einer Kommandozeile (die in der
Aufruferumgebung dem «eval»‐Kommando übergeben wird) werden
können.

Dazu kann man entweder nicht zum POSIX‐Standard kompatibel die
Formatierungsanweisung «%q» des in den Bash eingebauten
«printf»‐Kommandos oder auch des selbständigen «printf»‐Programms
nutzen; oder man schreibt (zu POSIX kompatibel) eine dem
entsprechende Funktion (oder ein Shell‐Skript) selbst.

Um zu erklären, worum es dabei geht, stell dir vor, einer der
(nicht‐robusten) Pfade sei der folgende Text:

«"Hallo, wie geht's?", fragte er.»

Dateinamen können in Unix ja alle möglichen Zeichen außer dem
ASCII null enthalten, also könnte das ein Pfad sein.

Robust gemacht, käme dabei folgendes heraus:


«./"Hallo, wie geht's?", fragte er.»

Nur kann man das so nicht in eine Kommandozeile setzen, denn die
Anführungszeichen werden vom Kommandozeilenparser gefressen, und
die nicht durch Anführungszeichen, Apostrophe oder umgekehrte
Schrägstriche geschützten Leerstellen führen zum Zerbrechen des
Pfads, und es entstehen drei Teile:

«./Hallo, wie geht's?,» «fragte» und «er.».

So darf das das Shell‐Skript «my_robuste_pfade» also nicht
ausgeben.

Wenn es einfach noch einen Apostroph vorne und hinten dranklebt,
wird's auch nicht besser:

«'./"Hallo, wie geht's?", fragte er.'»

=> Der dritte Apostroph führt zum Syntaxfehler in der
Kommandozeile.

Und Anführungszeichen statt Apostrophe vorne und hinten
dranzukleben, funktioniert auch nicht, weil dem die bereits
darin enthaltenen Anführungszeichen in die Quere kommen:

«"./"Hallo, wie geht's?", fragte er."»

=> Der Apostroph führt zum Syntaxfehler in der Kommandozeile.


Funktionieren würde jedoch beispielsweise, wenn das Shell‐Skript
folgenden Text ausgäbe,

«'./"Hallo, wie geht'\''s?", fragte er.'»

bei dem der ganze Text in Apostrophe eingefasst ist.  Dadurch
sind alle Zeichen außer den Apostrophen vor dem Parser
geschützt.  Apostrophe kann man mit Apostrophen nicht einfassen,
deshalb wendet man da einen Kniff an:  Kommt ein Apostroph in der
Zeichenfolge vor, setzt man zunächst einen Apostroph hin, um die
Einfassung mit Apostrophen zu beenden, schützt dann den
vorhandenen Apostroph mit einem «\» und setzt danach noch einen
Apostroph hin, um die Einfassung mit Apostrophen wieder
aufzunehmen:  Aus «'» wird «'\''».

Also braucht das Shell‐Skript innen drin eine Funktion, die genau
das macht.

Wenn man will, kann man mehrere unmittelbar hintereinander
auftretende Apostrophe, statt jeden einzeln zu schützen

«mit drei ''' Apostrophen» => «'mit drei '\'\'\'' Apostrophen'»,


gemeinsam in Anführungszeichen stellen:


«mit drei ''' Apostrophen» => «'mit drei '"'''"' Apostrophen'»

Das ist dann etwas kürzer und, falls man es lesen will, auch
leichter zu verstehen.

Die Funktion, die das macht, nenne ich
«my_quote_words_for_shells».  Sie kann beispielsweise so
aussehen:

my_quote_words_for_shells()
(
# gibt eine Wortliste so aus, dass sie als (Teil einer)
# Parameterliste in die Kommandozeile eines POSIX-Shells
# eingefuegt werden kann.
#
# Exit-Code:
# 0, falls die Umsetzung ohne Fehler erfolgt ist,
# !=0, falls ein Fehler aufgetreten ist.
#
# Beispiele:
#
# eine Wortliste als Kommandoaufruf in eine Kommandozeile
# stellen:
#
# if kommandozeile="$(
# my_quote_words_for_shells Programm mit Parametern
# )"
# then
# # verwende sie mittels "eval":
# eval " $kommandozeile"
#
# # oder gib sie einem neuen Shell:
# sh -c -- "$kommandozeile" sh
#
# # oder reiche sie an "su" weiter:
# su -- - ein_Benutzer -c -- "$kommandozeile" -su
#
# # oder verwende sie auf einem anderen Rechner:
# ssh benu...@remote.host.example " $kommandozeile"
#
# else
# # Ein Fehler ist bei der Errechnung der Kommandozeile
# # aufgetreten.
# fi
#
#
# Die positional parameters in einer Variablen speichern und
# wieder herstellen:
#
# if args="$(my_quote_words_for_shells "$@")"
# then
# # irgend etwas mit den positional parameters anstellen.
# # ...
#
# # die vorherigen positional parameters wieder herstellen:
#
# eval "set -- $args"
# else
# # Ein Fehler ist beim Sichern der positional parameters
# # aufgetreten.
# fi

# "wordsep" enthaelt ein Trennzeichen fuer die Liste der
# maskierten Woerter. Vor dem ersten Wort braucht keines
# ausgegeben zu werden:

wordsep=
for word
do
# "$wordsep" trennt jedes (auszer das erste) auszugebende Wort
# von seinem Vorgaenger:
printf '%s' "$wordsep"
if test -z "$word"
then
# Es liegt das leere Wort vor. Dann ist das Ergebnis "''":
printf '%s' "''"
else
# Das Wort ist nicht leer.
while
{
prefix="${word%%\'*}"
word="${word#"$prefix"}"
# "$prefix" ist das laengste Anfangsstueck von "$word",
# das keinen Apostroph enthaelt; "$word" erhaelt den Rest.
# Folgerung: "$word" ist entweder leer oder beginnt mit
# einem Apostroph.
if test -n "$prefix"
then
# "$prefix" besteht aus mehr als null Stueck Zeichen.
# Setze sie zwischen zwei Apostrophe:
printf \''%s'\' "${prefix}"
fi
test -n "$word" &&
{
# "$word" ist nicht leer. Folgerung: "$word" beginnt mit
# mindestens einem Apostroph.
apostr="${word%%[!\']*}"
# "$apostr" ist das laengste Anfangsstueck von "$word",
# das nur aus Apostrophen besteht.
if test -n "${apostr#"'"}"
then
# Es hat mindestens 2 Apostrophe: Setze es zwischen
# zwei Anfuehrungszeichen:
printf '"%s"' "${apostr}"
else
# Es besteht aus 1 Apostroph: Stelle ihm einen
# umgekehrten Schraegstrich voran:
printf '\\%s' "${apostr}"
fi
# Schneide das Apostrophe-Anfangsstueck von "$word" ab:
word="${word#"$apostr"}"
# Bleibt nichts mehr uebrig, ist das Wort fertig
# verarbeitet:
${word:+:} false
}
}
do
:
done
fi
# Alle weiteren Woerter (auszer dem ersten) muessen von ihrem
# Vorgaenger mit einem Leerzeichen abgetrennt werden:
wordsep=' '
done
printf '\n'
)

Die stellt man dann in das Shell‐Skript «my_robuste_pfade» hinein
und erhält, wenn jetzt keine Fehler mehr drin sind, eine wirklich
robuste Lösung, die robuste Pfade liefert.  Sie hat die Option
«-h» für einen kurzen Hilfetext und muss deswegen natürlich auch
den speziellen Optionen‐Ende‐Parameter «--» verwenden:

#!/bin/sh -
# Fassung vom 2022-08-30T18:00:44+02:00

set -ue
readonly -- prgnam="${0}" exit_failure=1 exit_syntax=2 \
exit_program_error=3

syntax()
{
{
if ${1+:} false
then
cut -d . -f2- <<-EOF
.${prgnam}: $@
.
EOF
fi
cut -d . -f2- <<-EOF
.$prgnam
.
.Syntax:
.
.$prgnam -h
.
.$prgnam -W help
.
.Optionen:
.
. -h, -W help
.
. Zeige diesen Hilfetext
.
.Exit statuses:
.
.0 Erfolg
.
.1 Fehler
.
.2 Aufrufsyntaxfehler
.
.3 Programmfehler
EOF
} |
output_formatted
} # syntax()

columns="${COLUMNS:-}"
${columns:+:} false ||
{
columns="$( tput cols )" &&
LC_ALL=C expr " $columns" : ' [[:digit:]]\{1,\}$' \
> /dev/null && test "$columns" -gt 0
} || columns=80
for foldcmdline in \
'fmt ${columns:+-w} ${columns:+"$columns"} --' \
'fold -s ${columns:+-w} ${columns:+"$columns"} --' \
'cat --'
do
(
eval "set '' $foldcmdline" && shift &&
command -v -- "$1" > /dev/null 2>&1
) && break
done

eval 'output_formatted() { '"$foldcmdline"'; }'

option_help=false &&
while getopts hW: option
do
case "$option"
in
(h)
option_help=:
;;
(W)
case "${OPTARG-?}"
in
(help)
option_help=:
;;
(*)
{
printf 'Unbekannter Parameter der Option -W: %s\n' \
"$OPTARG"
syntax ${1+"$@"}
} >&2
exit "$exit_syntax"
;;
esac
;;
(\?)
syntax >&2 ${1+"$@"}
exit "$exit_syntax"
;;
(*)
printf >&2 \
'%s:\nProgrammfehler: Optionsbehandlung fuer %s fehlt.\n' \
"$prgnam" "$option"
exit "$exit_program_error"
;;
esac
done &&
shift $((OPTIND - 1)) &&
if "$option_help"
then
syntax
exit
fi &&

my_quote_words_for_shells()
(
# gibt eine Wortliste so aus, dass sie als (Teil einer)
# Parameterliste in die Kommandozeile eines POSIX-Shells
# eingefuegt werden kann.
#
# Exit-Code:
# 0, falls die Umsetzung ohne Fehler erfolgt ist,
# !=0, falls ein Fehler aufgetreten ist.
#
# Beispiele:
#
# eine Wortliste als Kommandoaufruf in eine Kommandozeile
# stellen:
#
# if kommandozeile="$(
# my_quote_words_for_shells Programm mit Parametern
# )"
# then
# # verwende sie mittels "eval":
# eval " $kommandozeile"
#
# # oder gib sie einem neuen Shell:
# sh -c -- "$kommandozeile" sh
#
# # oder reiche sie an "su" weiter:
# su -- - ein_Benutzer -c -- "$kommandozeile" -su
#
# # oder verwende sie auf einem anderen Rechner:
# ssh benu...@remote.host.example " $kommandozeile"
#
# else
# # Ein Fehler ist bei der Errechnung der Kommandozeile
# # aufgetreten.
# fi
#
#
# Die positional parameters in einer Variablen speichern und
# wieder herstellen:
#
# if args="$(my_quote_words_for_shells "$@")"
# then
# # irgend etwas mit den positional parameters anstellen.
# # ...
#
# # die vorherigen positional parameters wieder herstellen:
#
# eval "set -- $args"
# else
# # Ein Fehler ist beim Sichern der positional parameters
# # aufgetreten.
# fi

# "wordsep" enthaelt ein Trennzeichen fuer die Liste der
# maskierten Woerter. Vor dem ersten Wort braucht keines
# ausgegeben zu werden:

wordsep=
for word
do
# "$wordsep" trennt jedes (auszer das erste) auszugebende Wort
# von seinem Vorgaenger:
printf '%s' "$wordsep"
if test -z "$word"
then
# Es liegt das leere Wort vor. Dann ist das Ergebnis "''":
printf '%s' "''"
else
# Das Wort ist nicht leer.
while
{
prefix="${word%%\'*}"
word="${word#"$prefix"}"
# "$prefix" ist das laengste Anfangsstueck von "$word",
# das keinen Apostroph enthaelt; "$word" erhaelt den Rest.
# Folgerung: "$word" ist entweder leer oder beginnt mit
# einem Apostroph.
if test -n "$prefix"
then
# "$prefix" besteht aus mehr als null Stueck Zeichen.
# Setze sie zwischen zwei Apostrophe:
printf \''%s'\' "${prefix}"
fi
test -n "$word" &&
{
# "$word" ist nicht leer. Folgerung: "$word" beginnt mit
# mindestens einem Apostroph.
apostr="${word%%[!\']*}"
# "$apostr" ist das laengste Anfangsstueck von "$word",
# das nur aus Apostrophen besteht.
if test -n "${apostr#"'"}"
then
# Es hat mindestens 2 Apostrophe: Setze es zwischen
# zwei Anfuehrungszeichen:
printf '"%s"' "${apostr}"
else
# Es besteht aus 1 Apostroph: Stelle ihm einen
# umgekehrten Schraegstrich voran:
printf '\\%s' "${apostr}"
fi
# Schneide das Apostrophe-Anfangsstueck von "$word" ab:
word="${word#"$apostr"}"
# Bleibt nichts mehr uebrig, ist das Wort fertig
# verarbeitet:
${word:+:} false
}
}
do
:
done
fi
# Alle weiteren Woerter (auszer dem ersten) muessen von ihrem
# Vorgaenger mit einem Leerzeichen abgetrennt werden:
wordsep=' '
done
printf '\n'
) &&

for pfad
do
case "${pfad%%/*}" in
(.|'')
# Alles, was mit der Komponente '.' oder '' beginnt,
# ist schon robust und kann bleiben, wie es ist:
#
# Meines Wissens muss in jedem "case"-Zweig
# wenigstens ein Kommando stehen, also nehme ich
# hier das Kommando ":", das nichts tut:
# (Falls ich mich irre, kann man es auch weglassen.)
:
;;
(*)
# Alles andere wird robust gemacht durch Voranstellen
# von './':
#
pfad=./"$pfad"
;;
esac &&
shift &&
set -- "$@" "$pfad"
done &&
my_quote_words_for_shells "$@"

Marcel Logen

unread,
Sep 12, 2022, 10:16:23 AM9/12/22
to
Helmut Waitzmann in de.comp.os.unix.shell:

[...]

>Bei der Kommandozeile
>
> eval "$name_der_ergebnisvariablen"=…
>
>ist das anders:  Das ist (zunächst) keine Zuweisung sondern tatsächlich
>nur ein simple command: «eval».  Bevor «eval» schließlich startet,
>geschieht die Variablenexpansion.  Wenn die Variable
>«name_der_ergebnisvariablen» beispielsweise den Wert «ergebnis» hat,
>bekommt «eval» folgenden Text übergeben:
>
> eval ergebnis=…
>
>Weil «eval» den Kommandozeilenparser (nochmals) anwirft, sieht der jetzt
>«ergebnis=…» und erkennt eine Zuweisung an die Variable «ergebnis».

Danke für Deine Erläuterungen.

Ich habe mal in POSIX nach "eval" gesucht, hatte es da aber zunächst
nicht gefunden, später aber doch:

<https://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_18_23>

Marcel 1v5v (64703)
--
╭────╮ ╭───╮ ╭─╮ ╭───────────╮ ╭────────╮
╭─╯ ╰─╮ ╭─────╯ ╰─╯ │ ╰──────╮ ╭─╯ │ ╭─╯
│ │ ╰──╮ ╭──╯ ╭─╮ ╭─╮ │ ╰─╮ ╭─╮ ╭─╯ ╭───╯ ╭───────╮
──╯ ╰─────╯ ╰─────╯ ╰─╯ ╰─╯ ╰─╯ ╰─╯ ╰───────╯ 854520╰
0 new messages