staging.inyokaproject.org

Multi-Shell Arrays & Dictionaries

Status: Ungelöst | Ubuntu-Version: Ubuntu 18.04 (Bionic Beaver)
Antworten |

tokiox

Anmeldungsdatum:
29. Juni 2012

Beiträge: 56

Moin,

falls ich diesen Thread im falschen Bereich eingefügt habe, bitte nicht sauer sein und verschieben.

Ich habe mir etwas recht Umfangreiches vorgenommen und stehe aktuell noch am Anfang. Ich arbeite mich gerade in verschiedenste Shells (bash, dash, ksh, mksh, posh, sh, zsh) ein. Bash funktioniert schon ganz gut. Die Problematik besteht in den teils feinen Unterschieden zwischen den verschiedenen Shells.

Was ich eigentlich vorhabe!

Ich versuche mir ein Script zu erstellen, welches ich in meine zukünftigen Projekte einbinden kann. Dieses soll ermöglichen, dass das Hauptscript auf möglich vielen Shells genutzt werden kann.

Aktuell befasse ich mich mit den Arrays und Dictionaries, welche teils nicht von den Shells unterstützt werden.

Es geht vorrangig darum, die Machbarkeit zu prüfen. Dass ich aktuell noch viel mit eval arbeite, ist natürlich auch noch eine Baustelle und soll auch noch gelöst werden.

Die folgende Tabelle ist hoffentlich nachvollziehbar und erläutert die gewünschten Ziele der Funktion ARRAY.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
			Funkt.	Name		Option	Richt.	Anzahl Elemente variabel 
								Reihenfolge beliebig
								Auswahlbereich von 1 – n
								1. Element			n. Element
---------------------------------------------------------------------------------------------------------------
Erstelle Array 
mit Elementen im 
Bereich und gleichem 
Wert 
Wenn vorhanden ersetze	ARRAY	arrayname	-c	y	[1]		′-	…	[n] “value”
Erstelle Array 
mit Elementen und 
gleichem Wert 
Wenn Array vorhanden 
wird dieses ersetzt	ARRAY	arrayname	-c	y	[1]		′+	…	[n] “value”
Erstelle Array 
mit Elementen 
und Werten 
Wenn Array vorhanden 
wird dieses ersetzt	ARRAY	arrayname	-c	y	[1] “value_1”	′+	…	[n] “value_n”
Erstelle Elemente 
mit Werten 
Wenn vorhanden 
ersetze alte Werte	ARRAY	arrayname	-c		[1] “value_1”		…	[n] “value_n”
Erstelle Array 
Wenn vorhanden 
ersetze mit 
leerem Array	        ARRAY	arrayname	-c							
Lösche Elemente im 
Bereich	                ARRAY	arrayname	-r		[1]		′-	…	[n]
Lösche Elemente	        ARRAY	arrayname	-r		[1]			…	[n]	
Lösche Array		ARRAY	arrayname	-r							
Ergänze Elemente 
im Bereich um 
gleichen Wert		ARRAY	arrayname		(y)	[1]		′-	…	[n] “value”
Ergänze Elemente 
um gleichen Wert	ARRAY	arrayname		(y)	[1]		′+	…	[n] “value”
Ergänze Elemente 
um Wert			ARRAY	arrayname		(y)	[1] “value_1”		…	[n] “value_n”
Ausgeben Elemente 
im Bereich als Liste	ARRAY	arrayname		y	[1]		′-	…	[n]	
Ausgeben Elemente 
als Liste		ARRAY	arrayname		y	[1]			…	[n]	
Ausgeben Array als 
Liste			ARRAY	arrayname		y						
Ausgeben Länge 
für Elemente im 
Bereich als Liste	ARRAY	arrayname	-l		[1]		′-	…	[n]	
Ausgeben Länge 
für Element als Liste	ARRAY	arrayname	-l		[1]			…	[n]	
Ausgeben Länge 
für Array		ARRAY	arrayname	-l							
Ausgeben 
Arrayschlüssel 
Elemente im 
Bereich als Liste 
wenn vorhanden		ARRAY	arrayname	-k	(y)	[1]		′-	…	[n]	
Ausgeben 
Arrayschlüssel 
Elemente als Liste 
wenn vorhanden		ARRAY	arrayname	-k	(y)	[1]			…	[n]	
Ausgeben 
Arrayschlüssel 
als Liste		ARRAY	arrayname	-k	(y)						

Hier nun der bisherige Code:

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
set -eu

# currently used Shell / aktuell verwendete Shell
shell_pid=$$ ; shell_name=$( ps -o command= -p $shell_pid | cut -d" " -f1 )
printf "shell_name: $shell_name\n"

ARRAY () {
    echo argumente: $*
    arrayname=$1; shift
    #echo argument: ${1-}
    arrayoption=
    case ${1-} in
        -c) arrayoption=cre; shift;;
        -r) arrayoption=rem; shift;;
        -l) arrayoption=len; shift;;
        -k) arrayoption=key; shift;;
    esac
    #echo argument: ${1-}
    arrayorder=
    case ${1-} in
        -a) arrayorder="-a"; shift;;
        -d) arrayorder="-d"; shift;;
        -s) arrayorder="-s"; shift;;
    esac
    if [ "$arrayoption" = "rem" ] || [ "$arrayoption" = "len" ]; then arrayorder=; fi

    echo arrayname: ${arrayname}
    echo arrayorder: ${arrayorder-}
    arrayrest=${*-}
    #echo arrayrest: $arrayrest

    unsetlist="arrayname arrayoption arrayorder arrayrest"
    cpos=0
    if [ -n "$arrayrest" ]; then
        for i in $( echo "$arrayrest" ); do
            case "$i" in
                \[*\])
                    case "$shell_name" in
                        zsh)
                            cpos=$(( $cpos + 1 ));
                            d=${i%]}
                            eval "position_${cpos}=\"${d#'['}\"";
                            unsetlist="$unsetlist position_${cpos}"
                            echo "position_${cpos}: $( eval echo \${position_${cpos}} )"
                            ;;
                        *)
                            cpos=$(( $cpos + 1 ));
                            d=${i%]}
                            eval "position_${cpos}=\"${d#[}\"";
                            unsetlist="$unsetlist position_${cpos}"
                            echo "position_${cpos}: $( eval echo \${position_${cpos}} )"
                            ;;
                    esac
                    ;;
                ′-|′+)
                    eval "event_${cpos}=\"${i}\""
                    unsetlist="$unsetlist event_${cpos}"
                    echo "event_${cpos}: $( eval echo \${event_${cpos}} )"
                    ;;
                *)
                    eval "value_${cpos}=\"$( eval echo \${value_${cpos}-} ) ${i}\""
                    unsetlist="$unsetlist value_${cpos}"
                    echo "value_${cpos}: $( eval echo \${value_${cpos}} )"
                    ;;
            esac
        done
    fi

    case ${arrayoption-} in
        cre)
            echo arrayoption: $arrayoption
            if [ "$cpos" -gt 0 ]; then
                arrayrange=1
                if [ "$cpos" -gt 1 ]; then
                    arrayrange=$( echo "{1..$cpos}" )
                fi
                echo arrayrange: $arrayrange
                unsetlist="$unsetlist arraykeys arrayrange"
                case "$shell_name" in
                    dash|sh)
                        for i in $arrayrange; do
                            eval "${arrayname}_$( eval echo \${position_${i}} )=\"$( eval echo \${value_${i}-} )\"";
                            arraykeys="${arraykeys-} $( eval echo \${position_${i}} )"
                            echo "${arrayname}_$( eval echo \${position_${i}} ): $( eval echo \${${arrayname}_$( eval echo \${position_${i}} )} )"
                        done
                        ;;
                    zsh)
                        for i in $arrayrange; do
                            echo "${arrayname}[$( eval echo \${position_${i}} )]=\"$( eval echo \${value_${i}-} )\"";
                            eval echo "${arrayname}[$( eval echo \${position_${i}} )]=\"$( eval echo \${value_${i}-} )\"";
                            arraykeys="${arraykeys-} $( eval echo \${position_${i}} )"
                            echo \${$( echo \${${arrayname}[$( eval echo \${position_${i}} )]} )}
                        done
                        ;;
                    *)
                        for i in $arrayrange; do
                            eval "${arrayname}[$( eval echo \${position_${i}} )]=\"$( eval echo \${value_${i}-} )\"";
                            arraykeys="${arraykeys-} $( eval echo \${position_${i}} )"
                            echo "${arrayname}[$( eval echo \${position_${i}} )]: $( eval echo \${${arrayname}[$( eval echo \${position_${i}} )]} )"
                        done
                        ;;
                esac
            fi
            eval "${arrayname}_=\"$cpos ${arrayorder:--a} ${arraykeys-0}\"";
            echo "${arrayname}_: $( eval echo \${${arrayname}_} )"
            ;;
        #
    esac

    unset $( echo $unsetlist )
}

# ARRAY arr -c
ARRAY arr -c -a "[5] eins" # ′+ [3] zwei [2] drei [4] vier ′- [1] fünf"
# ARRAY arr -c -d "[5] eins ′+ [3] zwei [2] drei [4] vier ′- [1] fünf"
# ARRAY arr -c -s "[5] eins ′+ [3] zwei [2] drei [4] vier ′- [1] fünf"
# ARRAY arr -r "[2] [1]"
# ARRAY arr -r -a "[5]"
# ARRAY arr -l
# ARRAY arr -l -s
# ARRAY arr -k
# ARRAY arr -k -a
# ARRAY arr -k -d
# ARRAY arr -k -s
# ARRAY arr "[1]"
# ARRAY arr "[1] 123"
# ARRAY arr

Für echo habe ich aktuell folgenden Code, Wobei die Verfügbarkeit des externen echo-Befehls noch fehlt.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
# exit if any command fails or variables/arrays are not set / beendet wenn irgend ein Kommando scheitert oder Variablen/Arrays nicht gesetzt sind
set -eu
# used internal field separators / genutzte interne Feldseperatoren
#----
IFSold=${IFS}
IFSnew=$'\n\t'
IFS=$IFSnew

ECHO () {
    IFS=$IFSold
    case "$shell_name" in
      bash|ksh) echo $*;;
      dash|posh|sh)     
		case "${1:-}" in 
			-en) shift; echo -n $*;; 
			-e) shift; echo $*;; 
			-n) /bin/echo -n $*;; 
			*) /bin/echo $*;; esac;;
      *)        /bin/echo $*;;
    esac
    IFS=$IFSnew
}

Ich hoffe, Ihr habt Ideen und ich kann noch was lernen.

Danke schon jetzt!

Bearbeitet von rklm:

Syntaxhighlighting. Bitte bekannte Namen für Syntax benutzen

rklm Team-Icon

Projektleitung

Anmeldungsdatum:
16. Oktober 2011

Beiträge: 13242

tokiox schrieb:

Ich versuche mir ein Script zu erstellen, welches ich in meine zukünftigen Projekte einbinden kann. Dieses soll ermöglichen, dass das Hauptscript auf möglich vielen Shells genutzt werden kann.

Ich würde mich einfach auf den POSIX-Standard beschränken und gut. Das bedeutet, nur Bourne Shell Syntax, keine Arrays etc.

Aktuell befasse ich mich mit den Arrays und Dictionaries, welche teils nicht von den Shells unterstützt werden.

Das klingt so, dass Du Dein Problem eher in einer der bekannten Skriptsprachen (Python3, Ruby, Lua) implementieren solltest als mit der Shell. Wobei, Dictionaries kannst Du auch in awk haben. Hängt davon ab, was Du machen willst, ob awk das passende Werkzeug ist. Für manche Probleme kann ein awk-Skript richtig schön kurz sein.

Es geht vorrangig darum, die Machbarkeit zu prüfen. Dass ich aktuell noch viel mit eval arbeite, ist natürlich auch noch eine Baustelle und soll auch noch gelöst werden.

Ja, eval hat so seine Sicherheits- und Robustheitsprobleme. Nicht so gut.

Die erste Zeile kannst Du einfacher so haben:

1
shell_name=$(ps -o comm --no-headers -p $$)

tokiox

(Themenstarter)

Anmeldungsdatum:
29. Juni 2012

Beiträge: 56

Danke fürs Reaktivieren meiner Berechtigungen.

rklm schrieb:

Ich würde mich einfach auf den POSIX-Standard beschränken und gut. Das bedeutet, nur Bourne Shell Syntax, keine Arrays etc.

Schon richtig, allerdings bin ich mir nicht sicher, ob dann ggf. zu viele Variablen von vorn herein notwendig werden, wie man bereits an sh erkennen kann. POSIX scheint jedoch auch nicht der Weisheit letzter Schluss zu sein. So ein Hilfsscript kann dann ggf. nützlich sein, da die zusätzlichen Variablen automatisch erstellt werden. Die in case für sh vorgesehene Lösung funktioniert auch in den anderen Shells.

Das klingt so, dass Du Dein Problem eher in einer der bekannten Skriptsprachen (Python3, Ruby, Lua) implementieren solltest als mit der Shell. Wobei, Dictionaries kannst Du auch in awk haben. Hängt davon ab, was Du machen willst, ob awk das passende Werkzeug ist. Für manche Probleme kann ein awk-Skript richtig schön kurz sein.

Python3, Ruby, Lua sind allerdings nicht immer installiert, gerade auf Live-Medien. Wobei, bei Python bin ich mir da gerade gar nicht so sicher. Lua werde ich für ein spezielles Projekt nutzen, allerdings möchte ich hier möglichst viel über die verschiedenen Shells herausfinden und mich möglichst innerhalb der Shells bewegen.

Mit awk muss ich mich erst noch beschäftigen.

Ja, eval hat so seine Sicherheits- und Robustheitsprobleme. Nicht so gut.

Ja! Nur lief diese Variante auf allen Shells direkt. Alternative, möglichst only-Shell-Ansätze sind immer gern gesehen.

Die erste Zeile kannst Du einfacher so haben:

1
shell_name=$(ps -o comm --no-headers -p $$)

Integriert, mit .shall auf allen Shells geprüft. Danke!

Wollte eigentlich auch csh tcsh und fish mit integrieren. Das hab ich allerdings aufgrund der zu unterschiedlichen Syntax aufgegeben.

rklm Team-Icon

Projektleitung

Anmeldungsdatum:
16. Oktober 2011

Beiträge: 13242

tokiox schrieb:

Danke fürs Reaktivieren meiner Berechtigungen.

???

Python3, Ruby, Lua sind allerdings nicht immer installiert, gerade auf Live-Medien.

Die Anzahl der installierten Shells ist da auch sehr eingeschränkt, bei den Ubuntus findest Du da sowieso nur die sh, bash und vielleicht die dash, die aber auch nur eine sh ist. Da bist Du auf jeden Fall am besten beraten, wenn Du Dich auf die sh und den POSIX-Umfang beschränkst. Man bekommt auch erstaunlich viel damit hin und braucht nur selten Arrays. Übrigens: auch die sh unterstützt Arrays, und zwar die Argumente des Skriptes und die Argumente aufgerufener Shell-Funktionen.

Wollte eigentlich auch csh tcsh und fish mit integrieren. Das hab ich allerdings aufgrund der zu unterschiedlichen Syntax aufgegeben.

Ja, das sind halt keine Bourne-Shells. M.E. sind die sowieso tot. Die csh war immer schon eine Pein und die bash hat der tcsh m.E. schon längst den Rang abgelaufen, was die Nutzung angeht.

fork991

Anmeldungsdatum:
26. Mai 2010

Beiträge: 58

Ich würde dem auch zustimmen. POSIX-Shell wäre die Option, wenn es um Kompatibilität geht.

Shellscripting ist keine richtige Programmiersprache

Für mich ist Shellscripting keine Programmiersprache. Ich habe auch schon längere Programme damit geschrieben und recht schnell sind für mich die Eigenschaften davon sehr anstrengend: Die Behandlung von Sonderzeichen, erfordert immer wieder spezielle Aufmerksamkeit. Ein verschachteltes Quoting wird recht schnell schlecht lesbar. Globale Variablen, sofern man sie denn nicht gründlich vermeidet. Natürlich auch die Performance, die aufgrund von dauernder Prozesserzeugung deutlich schlechter ist als die jeder normalen Scriptsprache. Shellscripting ist super für kurze Programme. Wenn's zu groß wird: Richtige Scriptsprache verwenden!

Shellscripting beruht auf die Fähigkeiten externer Programme

Aber eigentlich beruhen die Fähigkeiten der Shell ja nur zum Teil auf den Sprachkonstrukten des Interpreters. Shellprogrammierung ist zu einem großen Teil auch der Aufruf von externen Programmen. Und die unterscheiden sich mitunter doch erheblich. Spätestens wenn man auch Nicht-Linux-Systeme mit in Betracht zieht.

Verschiedene Varianten von Programmen

awk wird in Shellscripten auch gerne benutzt, weil es mit recht wenige Code effizient komplexe Aufgaben lösen kann. Aber es gibt da mehrere Varianten. Z. B. den ursprünglichen awk, nawk oder GNU awk. Die unterscheiden sich im Funktionsumfang erheblich.

Verschiedene Versionen von Programmen

Bei der Bash oder auch bei awk kommen immer wieder auch neue Möglichkeiten dazu. Ich habe das in beiden Fällen schon mal erleben müssen. Das ist aber auch Normalität bei Programmiersprachen, dass sich die immer mal wieder ändern und Anpassungen erforderlich sind. Auch wenn Shellscripte auch älteren Systemen laufen sollen, dann wird man merken, was alles bricht, weil die Features der Shell bzw. der externen Programme damals noch nicht implementiert waren.

Gute Möglichkeit: Perl 5

Wenn Portabilität wirklich wichtig ist, dann würde ich dazu Perl 5 verwenden, genauer gesagt eine Version von ungefähr Perl 5.0 (Aktuelle Version zum Zeitpunkt des Beitrages: 5.32.1). Aktuell dürfte das wirklich überall vorhanden sein - sogar im Unix Bereich. Perl ist jetzt wirklich nicht gerade beliebt. Eher das Gegenteil dürfte der Fall sein: Es ist verschrien als unmodern und häßlich. Es ist aber eine Scriptsprache, die einen guten allgemein brauchbaren Umfang hat - sogar Objektorientierung ist möglich - um so eher wenig auf externe Programme zugreifen zu müssen. Es ist hinsichtlich der Performance Shellscripten natürlich überlegen. Natürlich sollte man auf Module weitgehend verzichten. Maximal Module aus dem Perl-Core verwenden. Andere werden wohl nicht zuverlässig vorhanden sein.

Das Werkzeug INXI(Anzeige von Hardware-Informationen) verwendet diesen Ansatz. Ursprünglich verwendete es mal bash + GNU awk. Aber irgendwann entschloss sich der Autor auf Perl zu wechseln - aus den hier geschilderten Gründen. Auch Proxmox setzt in vielen seiner beliebten OSS Projekte Perl ein. Das ist vermutlich historisch gewachsen und zu aufwändig neu in einer anderen Sprache zu implementieren. Ob man dort heute noch Perl einsetzen würde, würde man neu beginnen? Wahrscheinlich nicht.

rklm Team-Icon

Projektleitung

Anmeldungsdatum:
16. Oktober 2011

Beiträge: 13242

fork991 schrieb:

Shellscripting ist keine richtige Programmiersprache

Das ist nur in einem pragmatischen Sinn korrekt: Shell-Skript ist auf jeden Fall turing-vollständig und damit formal eine "richtige" Programmiersprache. Du hast natürlich Recht mit allen Problemen, die Du so auflistest.

awk wird in Shellscripten auch gerne benutzt, weil es mit recht wenige Code effizient komplexe Aufgaben lösen kann. Aber es gibt da mehrere Varianten. Z. B. den ursprünglichen awk, nawk oder GNU awk. Die unterscheiden sich im Funktionsumfang erheblich.

Naja, man kann sich auch bei awk auf einen kleinen Funktionsumfang beschränken. Dann ist es immer noch ein sehr mächtiges Werkzeug für eine bestimmte Klasse von Problemen (große Datenmengen aggregieren und auswerten).

Gute Möglichkeit: Perl 5

Wenn Portabilität wirklich wichtig ist, dann würde ich dazu Perl 5 verwenden, genauer gesagt eine Version von ungefähr Perl 5.0 (Aktuelle Version zum Zeitpunkt des Beitrages: 5.32.1). Aktuell dürfte das wirklich überall vorhanden sein - sogar im Unix Bereich. Perl ist jetzt wirklich nicht gerade beliebt. Eher das Gegenteil dürfte der Fall sein: Es ist verschrien als unmodern und häßlich. Es ist aber eine Scriptsprache, die einen guten allgemein brauchbaren Umfang hat - sogar Objektorientierung ist möglich - um so eher wenig auf externe Programme zugreifen zu müssen. Es ist hinsichtlich der Performance Shellscripten natürlich überlegen. Natürlich sollte man auf Module weitgehend verzichten. Maximal Module aus dem Perl-Core verwenden. Andere werden wohl nicht zuverlässig vorhanden sein.

Bevor ich Perl nutze, tue ich mir lieber die Schmerzen an, Ruby auf das System zu bringen. Alles, was Du schreibst, stimmt, aber die Objektorientierung ist eine hinterher angeflanschte Katastrophe und es gibt so viele Sonderlocken und Ausnahmen, dass ein Perl-Programm nach kurzer Lagerung bereits unlesbar geworden ist. Neeneenee.

tokiox

(Themenstarter)

Anmeldungsdatum:
29. Juni 2012

Beiträge: 56

Ja, Shellscripting ist keine der Programmierumgebungen, die man als besonders zeitgemäß oder komfortabel, geschweige denn mit übertriebenem Funktionsumfang ansehen kann. Doch ist es die wohl am häufigsten leicht zugängliche Möglichkeit. Wobei C hier ggf. noch in Frage kommt. Bei den meisten anderen Programmierumgebungen muss der Endanwender erst zusätzliche Ressourcen einspielen. Vielleicht liege ich dabei auch falsch.

Werde wohl die reine sh-Variante mit vielen einzelnen Variablen nutzen. Nur soll eval möglichst ersetzt werden und da muss ich nun erst mal schauen, ggf. exec oder $( ... ) Konstrukte.

Antworten |