Gibt es eine Möglichkeit, das $@-Array in einem POSIX-Shell-Skript zu ändern?

Gibt es eine Möglichkeit, das $@-Array in einem POSIX-Shell-Skript zu ändern?

Ich weiß vielleicht, wie man readlinknormale Dateien aus SymLinks erhält. Ich hatte diese sehr einfache Idee, jeden SymLink, der $@an meine Funktion übergeben wird, wie folgt zu ersetzen:

for file in "$@"; do
    [ -L "$file" ] && file=$(readlink -f "$file")
done

Ein Beispiel wäre ein SymLink /etc/os-release -> ../usr/lib/os-release.

Aber wenn ich das mache:

set -- '/etc/os-release'; for file in "$@"; do [ -L "$file" ] && file=$(readlink -f "$file"); done; echo "$@"

Ich erhalte immer noch den ursprünglichen SymLink-Pfad, was ... enttäuschend ist. Ich hatte gehofft, ihn direkt ändern zu können. $@Wenn das nicht möglich ist, gibt es irgendeinen Workaround?

Antwort1

Ihr Originalcode:

for file in "$@"; do
    [ -L "$file" ] && file=$(readlink -f -- "$file")
done

Das Problem besteht darin, dass die Einstellung filein der Schleife keine Auswirkungen auf den Inhalt der Liste der Positionsparameter hat.

Stattdessen können Sie verwenden

for file in "$@"; do
    [ -L "$file" ] && file=$(readlink -f -- "$file")
    set -- "$@" "$file"
    shift
done

Nun verschieben wir die Einträge vom Anfang "$@"nacheinander und fügen die möglicherweise geänderten Einträge am Ende der Liste hinzu. Das Ändern $@in einer forSchleife $@ist kein Problem, da forSchleifen immer über eine statische Liste iterieren, d. h. die Liste, über die iteriert wird, wird nur einmal am Anfang der Schleife ausgewertet.

Die obige Schleife setzt, wie die folgenden, bei jeder Iteration die gesamte Liste der Positionsparameter zurück. Für eineriesigListe von Dateinamen, kann dies bald ziemlich langsam werden. Wenn Ihre Shell Arrays hat,Stephen Kitts Antwortzeigt eine Möglichkeit, es zu beschleunigen.

Die Schleife könnte auch in einer etwas kürzeren Form geschrieben werden (Aufruf readlinkfür jedes einzelne Element),

for dummy do
    set -- "$@" "$(readlink -f -- "$1")"
    shift
done

Dies wird readlinkgemäß Ihrer Frage verwendet, obwohl es sich nicht um ein POSIX-Dienstprogramm handelt.

Beachten Sie, dass wenn die Ausgabe vonreadlink endet mit einem oder mehreren Zeilenumbrüchen(außer dem, der hinzugefügt wird, um die letzte Ausgabezeile zu beenden), würden diese entfernt. Dies ist eine Folge der Befehlsersetzung. Wenn dies ein Problem darstellt, fügen Sie der Befehlsersetzung künstlich ein abschließendes Zeichen hinzu und entfernen Sie es dann, wie mosvyvorgeschlagenin Kommentaren:

for file do
    [ -L "$file" ] && file=$(readlink -f -- "$file"; echo x)
    set -- "$@" "${file%x}"
    shift
done

Antwort2

Beachten Sie, dass weder readlinknoch realpathPOSIX-Befehle sind. Der POSIX-Befehlszeilen-Werkzeugkasten hat keine realpath()-ähnliche API. Hier readlinkkönnten Sie anstelle von (von dem es verschiedene inkompatible Implementierungen gibt) verwenden, zshdas ebenfalls eine realpath()-ähnliche Funktionalität mit seinem Modifikator integriert hat :P. In Ihrem shSkript:

eval "set -- $(zsh -c 'print -r ${(qq)argv:P}' zsh "$@")"

Sie könnten Ihr Skript aber auch zshgleich hier eintragen, wo es nur lauten würde:

argv=($argv:P)

Oder wenn python3es wahrscheinlicher verfügbar ist als zsh:

eval "set -- $(
  LC_ALL=C python3 -c '
import sys, os, shlex
print(" ".join(map(shlex.quote, map(os.path.realpath, sys.argv[1:]))))' "$@")"

verwandte Informationen