Was ist der Grund dafür, dass $ array nicht das gesamte Array in ksh und bash erweitert?

7

Inspiriert von dieser aktuellen Frage :

bash$ a=(1 2 3)
bash$ echo $a
1

aber

zsh% a=(1 2 3)
zsh% echo $a
1 2 3
zsh% printf '%s\n' $a
1
2
3

(Der letzte Teil zeigt, dass das Array in separate Argumente erweitert wird, die äquivalent zu "${a[@]}"und nicht sind. "${a[*]}")

Das Bash-Verhalten (das mit ksh übereinstimmt) ist äußerst kontraintuitiv. Wie ist "nur erstes Element" eine vernünftige Reaktion auf "diese Array-Variable erweitern"?

In anderen Bereichen, in denen zsh divergiert, liegen ksh und bash näher an der ursprünglichen Bourne-Shell. Bourne hatte jedoch keine benutzerdefinierten Array-Variablen.

Warum hat Bash diese seltsame Entscheidung getroffen? Wenn es ksh kopierte, warum traf ksh diese seltsame Entscheidung?

Fortsetzung nach einer langen Reihe von Kommentaren:

Dies sollte keine Frage der Kritik oder des Lobes von zsh sein. zsh ist nichts anderes als ein leicht zugängliches Beispiel dafür, wie Dinge anders gemacht worden sein könnten .

Eine der Möglichkeiten, eine Entwurfsentscheidung zu erklären, ist die Abwärtskompatibilität. Und Abwärtskompatibilität ist keine Meinung. Es ist eine objektive Tatsache.

Wenn Sie ein Skript anzeigen können (ein vollständiges Skript , kein Auszug außerhalb des Kontexts), das sich in der Bourne-Shell auf bekannte Weise verhält (dh nicht nur mit einem Syntaxfehler bombardiert) und sich in der hypothetischen "Korn-Shell" anders verhält mit voller $ array Erweiterung ", dann gewinnen Sie! Es ist ein Problem mit der Abwärtskompatibilität.

Ein solches Skript wurde nicht angegeben. Dies ist nicht einer:

a=(1 2 3)
printf '%s\n' $a

weil es ein Syntaxfehler in der Bourne-Shell ist. Wenn Sie einem früheren Syntaxfehler eine neue Bedeutung geben, können Sie neue Funktionen erstellen und gleichzeitig die Abwärtskompatibilität beibehalten.

Soweit ich das beurteilen kann, führt die Tatsache, dass a=(...)es sich ursprünglich um einen Syntaxfehler handelte, zu einer sauberen Trennung zwischen Skripten, die Arrays verwenden (versuchen) und solchen, die dies nicht tun. In der ersten Kategorie kann die Abwärtskompatibilität nicht als Grund für irgendetwas herangezogen werden, da diese Skripte ohnehin nicht in der alten Shell ausgeführt würden. In der zweiten Kategorie gilt die Abwärtskompatibilität unabhängig von den Erweiterungsregeln für Arrayvariablen, da keine zu erweiternden Arrays vorhanden sind!

Dies ist kein Beweis, da ich mich teilweise auf die Intuition verlasse, um zu entscheiden, dass es keine Möglichkeit gibt, ein Array ohne ein Skript zu schmuggeln, =(und daher kein Skript existiert, das inkompatibles Verhalten aufweisen würde. Das Schöne an einer Behauptung der Nichtexistenz ist, dass Sie nur ein Gegenbeispiel zeigen müssen, um sie zu beenden.

Die a=$@Sache, die in den Kommentaren angesprochen wurde, scheint zu einer Erklärung beizutragen. Wenn es eine Array-Variable erstellt, adann dieses Skript:

a=$@
printf '%s\n' $a

sollte den Unterschied zeigen. In meinen Tests passiert das jedoch nicht. Alle Muscheln (Erbstück sh, modernes ksh, bash und zsh) scheinen die erste Zeile auf die gleiche Weise zu behandeln. aist kein Array, sondern nur eine Zeichenfolge mit Leerzeichen. (zsh divergiert in der zweiten Zeile, weil es keine Wortteilung für den Wert von macht $a, aber das hat nichts mit Array-Variablen zu tun)


quelle
Bash kopierte ksh. (Ksh88 hatte bereits Arrays, Bash bekam sie erst 1996 mit Bash 2.0.)
Gilles 'SO - hör auf böse zu sein'
Wenn eine Variable aein Array in zsh enthalten könnte, warum in a=$@, $aist eine Zeichenfolge?
Isaac
Das scheint eine nicht verwandte Frage zu sein ... Die Antwort lautet, ob eine Zuweisung ein Array erstellt, hängt davon ab, ob unmittelbar nach dem =Zeichen eine linke Klammer steht . a=($@)kopiert das Array als Array.
Also, und Array: a=(1 2 3); printf '%s\n' $akonnte nicht mit 'b = $ a' kopiert werden, ist das nicht seltsam? In yash wird das Array kopiert b=$a.
Isaac
Es ist nicht "nur das erste Element", sondern ${array[0]}möglicherweise nicht vorhanden, insbesondere bei assoziativen Arrays.
Xhienne

Antworten:

2

Ich kann keine Antwort geben, aber einige mögliche Erklärungen vorschlagen.

Es stimmt , dass mit Ausnahme von KSH und ihre Klone (pdksh und weitere Derivate und bash), alle anderen Schalen mit Arrays ( csh, tcsh, rc, es, akanga, fish, zsh, yash) haben , $arrayum alle Mitglieder des Arrays zu erweitern.

Aber in beiden yashund zsh(wenn in der shEmulation), den beiden Bourne-ähnlichen Shells in dieser Liste, unterliegt diese Erweiterung immer noch dem Split + Glob (und der leeren Entfernung, zshauch wenn sie nicht in der shEmulation ist), sodass Sie immer noch die verwenden müssen umständliche "${array[@]}"Syntax (oder "${(@)array}"oder "$array[@]"in zshder es kaum einfacher zu tippen ist), um die Liste beizubehalten ( cshund tcshähnliche Probleme zu haben). Diese Split + Glob- und Leerentfernung ist das Bourne-Erbe (das in gewissem Maße durch das Thompson-Shell-Erbe verursacht wurde, das $1eher einer Makroerweiterung ähnelte).

rcund fishsind zwei Beispiele für spätere Granaten, die kein Bourne-Gepäck haben und sauberer sind. Sie erkennen die Tatsache an, dass eine Shell ein Befehlszeileninterpreter ist. Die primären Dinge, mit denen sie sich befassen, sind Listen (die Liste der Argumente für Befehle), also ist list / array der primäre Datentyp (es gibt nur einen Typ und seine Listen in rc) und Der Split + Glob-upon-Expansion-Fehler / die Fehlfunktion der Bourne-Shell (die jetzt, da der primäre Typ Array ist, nicht mehr benötigt wird) wurde beseitigt.

Dies erklärt jedoch nicht, warum David Korn sich dafür entschieden hat, nicht $arrayauf alle Elemente, sondern auf das Element des Index 0 zu erweitern.

Abgesehen von csh/ tcshsind all diese Shells jetzt viel neuer als die ksh, die in den frühen 80ern nur wenige Jahre nach der Veröffentlichung der Bourne-Shell und von Unix V7 entwickelt wurden. Unix V7 war dasjenige, das auch die Umgebung einführte. Das war damals die schicke neue Sache. Die Umgebung ist ordentlich und nützlich, aber Umgebungsvariablen können keine Arrays enthalten, es sei denn, Sie verwenden irgendeine Form der Codierung.

Das ist nur eine Vermutung, aber ich vermute, ein Grund für David Korn, diesen Ansatz zu wählen, war, dass die Schnittstelle zur Umgebung nicht geändert wurde.

In ksh88 waren wie rc alle Variablen Arrays (allerdings spärlich; ein bisschen wie assoziative Arrays mit Schlüsseln, die auf positive ganze Zahlen beschränkt sind, was eine weitere Kuriosität im Vergleich zu anderen Shells oder Programmiersprachen darstellt, und man konnte feststellen, dass sie nicht vollständig durchdacht waren es war zum Beispiel unmöglich, die Liste der Schlüssel abzurufen). In diesem neuen Design var=valuewurde kurz für var[0]=value. Sie können weiterhin alle Ihre Variablen export varexportieren , exportieren jedoch das Element des Index 0 des Arrays in die Umgebung.

rcsetzt alle seine Variablen in die Umgebung, fishunterstützt das Exportieren von Arrays, aber um dies für Arrays mit mehr als einem Element zu tun (zumindest für den Port zu Unix für rc, der von plan9 stammt), müssen sie auf irgendeine Form der Codierung zurückgreifen was nur von ihnen verstanden wird.

csh, tcsh, zshNicht unterstützt Arrays exportieren (obwohl heute , dass nicht wie eine große Einschränkung klingen mag). Sie können Arrays in yashexportieren, aber sie werden als Umgebungsvariable exportiert, mit der die Array-Elemente verknüpft sind :( (a "" "" b)und (a : b)daher auf denselben Wert exportiert werden), und beim Importieren wird keine Rückkonvertierung in ein Array durchgeführt.

Eine andere mögliche Rechtfertigung könnte die Konsistenz mit Bournes $@/ gewesen sein $*(aber warum beginnen Array-Indizes dann bei 0 statt bei 1 (eine weitere Kuriosität im Vergleich zu anderen Shells / Sprachen der Zeit)?). kshwar keine freie Software, es war ein kommerzielles Unternehmen, eine der Anforderungen war die Bourne-Kompatibilität. kshentfernte die Feldaufteilung, die für jedes nicht zitierte Wort im Listenkontext vorgenommen wurde (da dies in der Bourne-Shell eindeutig nicht nützlich war), musste sie jedoch für Erweiterungen behalten (da Skripte Dinge verwendeten, wie var="file1 file2"; cmd $vardie Bourne-Shell kein Array hatte, aber "$@") . Das in einer Shell zu belassen, die ansonsten Arrays enthält, macht wenig Sinn, aber Korn hatte kaum eine andere Option, wenn Ksh noch in der Lage sein sollte, Skripte der Verbraucherbasis zu interpretieren. Wenn$scalarunterlag split + glob, $arraymüsste aus Gründen der Konsistenz sein, und so machte "${array[@]}"eine Verallgemeinerung "$@"Sinn. zshhatte keine ähnliche Einschränkung, so dass es frei war, den split + glob bei Erweiterungen gleichzeitig mit dem Hinzufügen von Arrays zu entfernen (aber einen Preis für das Brechen von Bourne abwärtskompatibel zu zahlen).

Eine andere Erklärung , wie @Arrow angeboten hätte sein können , dass er nicht die bestehenden Betreiber zu überlasten wollen sie anders für verschiedene Arten von Variablen machen verhalten (zum Beispiel ${#var}vs ${#array}obwohl die Bourne - Shell nicht haben , dass ein oder ${var-value}, ${var#pattern}) dem Verwirrung bei den Benutzern verursachen ( zshes ist nicht immer offensichtlich, wie einige Operatoren mit Array oder Skalar arbeiten).

Einige verwandte Lektüre:


Was den a=$@Fall in Ihrer Bearbeitung betrifft, ist dies tatsächlich ein Fall, in dem ksh die Kompatibilität mit der Bourne-Shell unterbrochen hat.

In der Bourne-Shell $@und $*enthielt die Verkettung der Positionsparameter mit Leerzeichen. Nur $@wenn zitiert, war speziell, da es auf das gleiche erweitert wurde, "$*"aber mit den eingefügten Leerzeichen nicht zitiert (mit Sonderfällen für die leere Liste in den neueren Versionen, in denen es wie unter Solaris behandelt wurde). Sie werden feststellen, dass beim Entfernen von Leerzeichen $IFSin "$@"Listenkontexten nur ein Argument angezeigt wird (0 für eine leere Liste in den oben genannten festen Versionen). Wenn nicht angegeben, $*und $@verhalten sich wie jede andere Variable (split auf Zeichen von $IFS, nicht notwendigerweise auf den ursprünglichen Positionsparameter). Zum Beispiel in der Bourne-Shell:

'set' 'a:b'   'c'
IFS=:
printf '<%s>\n' $@
printf '[%s]\n' "$@"

Würde ausgeben:

<a>
<b c>
[a:b c]

Ksh88 änderte das so $@und $*wurde mit dem ersten Charakter von verbunden $IFS. "$@"Im Listenkontext werden die Positionsparameter getrennt, außer wenn sie $IFSleer sind.

Wenn $IFSleer ist, $*werden sie im Leerzeichen verbunden, außer $*wenn sie in Anführungszeichen stehen und ohne Trennzeichen verbunden werden.

Beispiele:

$ set a b
$ IFS=:
$ a=$@ b=$* c="$@" d="$*"
$ printf '<%s>\n' "$a" "$b" "$c" "$d" $@ $* "$@" "$*"
<a:b>
<a:b>
<a:b>
<a:b>
<a>
<b>
<a>
<b>
<a>
<b>
<a:b>
$ IFS=
$ a=$@ b=$* c="$@" d="$*"
$ printf '<%s>\n' "$a" "$b" "$c" "$d" $@ $* "$@" "$*"
<a b>
<a b>
<a b>
<ab>
<a b>
<a b>
<a b>
<ab>

Sie werden viele Variationen in den verschiedenen Bourne / Korn-ähnlichen Muscheln sehen, einschließlich ksh93 vs ksh88. Es gibt auch einige Variationen in Fällen wie:

set --
cmd ''"$@"
cmd $empty"$@"

Oder wenn es Mehrbytezeichen $IFSenthält oder Bytes keine gültigen Zeichen bilden.

Stéphane Chazelas
quelle
Beachten Sie, dass ksh, pdksh und weitere Ableitungen und bash mehr als 90% der Skripte mit Arrays bedeuten.
Isaac
Fazit: Abwärtskompatibilität, wie gesagt.
Isaac
@Arrow und bis Mitte der 2000er Jahre wurden 99% der Software für Microsoft Windows geschrieben, wohl das Betriebssystem mit dem schlechtesten Design, das jemals geschrieben wurde. Das sollte uns nicht davon abhalten, nach besseren Designs zu suchen.
Stéphane Chazelas
@Arrow, ja, Abwärtskompatibilität ist in der Tat das Wichtigste, was den technischen Fortschritt in der IT mehr als anderswo hemmt . An dem Tag, an dem Windows nicht mehr abwärtskompatibel ist, werden die Benutzer die Gelegenheit nutzen, etwas anderes auszuprobieren.
Stéphane Chazelas
Ob Sie Windows mögen oder nicht, ist hier absolut irrelevant. Bis Mitte der 2000er Jahre (wie jetzt) ​​wurden 99% der Software für die schnellsten Computer für Unix geschrieben (lesen Sie jetzt Unix und hauptsächlich Linux) . Microsoft war einfach irrelevant.
Isaac
1

Von Ihren Kommentaren zu dieser Antwort erwarten Sie, dass eine Antwort sagt und akzeptiert, dass das Paradigma von zsh "besser" ist und daher die Art und Weise, wie alle Shells funktionieren sollten. "Besser" ist nur eine Meinung. Eine Stellungnahme bedarf keiner Diskussion.

  • Vielleicht erwarten Sie das Detail, warum $aes auch für ein Array verwendet werden kann.

    Das ist es, was zsh versucht, und selbst wenn es viel Arbeit in diese Richtung geleistet hat,
    schlägt es in einigen Fällen immer noch fehl. Arrays werden (noch) nicht kopiert.

    $ a=(1 2 3)
    $ b=$a
    $ printf '<%s>' $a ' ' $b; echo
    <1><2><3>< ><1 2 3>

  • Aber diese Frage ist wirklich sehr weit davon entfernt. Dies ist ein Sprachproblem.

In unserer Sprache verwenden wir für jede Idee einen Namen. Jede neue Idee, die sich radikal von früheren Ideen unterscheidet, muss einen eigenen Namen haben. In unserer Sprache ist es selbstverständlich, dass wir neue Wörter für neue Ideen akzeptieren. Denken Sie an den Namen "Internet", einen neuen Namen für eine neue Idee. Die Verwendung alter Namen für neue Konzepte führt immer zu Verwirrung und Missverständnissen. So werden wir Menschen gebaut und es klingt vernünftig, wenn wir darüber nachdenken.

  • In der Shell (und jeder Programmiersprache) verwenden wir für jede bestimmte Idee eine bestimmte Syntax.

Von Anfang an verwendete eine Variable in einer Shell einen Namen (angenommen a) und ihr Wert war das Ergebnis der Erweiterung (einer Shell-Prozedur) der Symbole $a. Eine Variable konnte eine Zeichenfolge oder eine Zahl enthalten (automatisch konvertiert).

  • Die Einführung eines neuen Inhalts für eine Variable (ein Array von Werten) muss eine neue Syntax verwenden.

Das ist genau das, was Perl hat mit der Verwendung @azu bezeichnen , eine Liste , während hält $afür an scalar.
Das heißt: Die beiden oben genannten werden als SCALAR- und LIST-Kontext bezeichnet. ( In Perl ).

Aus diesem Grund müssen wir a=(1 2)ein Array zuweisen (es gibt auch andere Möglichkeiten, aber dies ist die häufigste). Eine Syntax, die in früheren Shells ungültig ist.

In sh (Strich in diesem Fall):

$ a=(12)
sh: 2: Syntax error: "(" unexpected

Und die Erweiterung der Variablen wurde von $aoder entsprechend ${a}der neuen Syntax erweitert (und in sh ungültig) `$ {a [@]}:

$ a=(aa bb cc)
$ printf '%s\n' "${a[@]}"
aa
bb
cc

In einfacheren Schalen (in diesem Fall Asche):

$ a=Strin-Of-Text
$ printf '%s\n' "$a"
ash: syntax error: bad substitution

Es ist bedauerlich, dass solch eine verschlungene Art des Schreibens eines Arrays gewählt wurde.

Wenn ich eine neue Syntax vorschlagen würde, würde ich wahrscheinlich dem Perl-Beispiel folgen und etwas Ähnliches wie "@a" für eine Liste von Werten verwenden (oder vielleicht #aoder %a). Das sollte Gegenstand einiger Diskussionen sein, um einen Konsens zu erzielen.

  • Aber das wurde (leider) nicht getan, sondern gewählt wurde : ${a[@]}.

Kurz gesagt: Aus Gründen der Abwärtskompatibilität wird erwartet, dass die Erweiterung einer einfachen Variablen $anur zu einem Wert führt, nicht zu einer Liste von Werten, sondern zu getrennten Werten.

Da in POSIX keine Arrays definiert sind, kann es unwirksam sein, zu zitieren, dass in POSIX die Definition der Parametererweiterung lautet :

Der Wert des Parameters, falls vorhanden, ist zu ersetzen.

Und tatsächlich drucken die meisten Muscheln nur einen Skalar mit $a

bash(4.4)       : <1><====><1><2><3>
lksh            : <1><====><1><2><3>
mksh            : <1><====><1><2><3>
ksh93           : <1><====><1><2><3>
astsh           : <1><====><1><2><3>
zsh/ksh         : <1><====><1><2><3>
zsh             : <1><2><3><====><1><2><3>

Für das Schreiben von Shell-Skripten ist zsh [a] also die ungerade.

Getestet mit diesem Skript:

a=(1 2 3)
printf '<%s>' $a '====';
printf '<%s>' "${a[@]}" ;
echo

[a] Auch yash. csh-kompatible Shells nicht getestet. Skripte mit csh sind ein bekanntes Problem.

Isaac
quelle
1
Es gibt viele Designfehler in der Bourne-Shell, die in zsh (im Standardmodus) behoben, aber aus Gründen der Abwärtskompatibilität in anderen Shells beibehalten werden. Dies ist keine Erklärung für die Array-Erweiterung, da Arrays in der Bourne-Shell nicht vorhanden waren. Sobald Sie eine Array-Zuweisung haben, befinden Sie sich außerhalb des Bereichs der Bourne-Kompatibilität. Ich finde das keine glaubwürdige Motivation.
Ich habe gesagt: "unwirksames Zitat". Die Antwort enthält noch weitere Elemente. @ WumpusQ.Wumbley
Isaac
2
Eine gute Antwort erfordert hier keinen Vergleich mit zsh. Es erfordert die Demonstration eines Problems, das bei der Kompatibilität zwischen Bourne und ksh aufgetreten wäre , wenn ksh von Anfang an den Ansatz "Gesamtes Array erweitern" gewählt hätte. Die Aufnahme von zsh in meine Frage dient nur dazu zu zeigen, dass der Ansatz machbar ist (und dass mindestens eine andere Person als ich es für eine gute Idee hielt, ihn umzusetzen!)
2
Ich verstehe den Anspruch auf Abwärtskompatibilität nicht. Zum Zeitpunkt der Erstellung der Arrays war ihre Verwendung überhaupt nicht mit allen vorherigen Shells kompatibel. Wie würde dann eine Abwärtskompatibilität in Betracht gezogen?
2
Aber nur, wenn Sie ein Array in Ihrem Skript verwendet haben, was es ohnehin nicht mit Shells kompatibel macht, die vor der Erfindung der Array-Variablen erstellt wurden!