|
kB
Supporter, Wikiteam
Anmeldungsdatum: 4. Oktober 2007
Beiträge: 10197
Wohnort: Münster
|
Für ein Bash-Skript benötige ich eine boolsche Funktion, die eine eingegebene Zeichenkette prüft, ob das ein zulässiger Name für eine benutzerdefinierte Variable ist. Bei der Bash muss ein zulässiger Name einer benutzerdefinierten Variable mit einem ASCII-Buchstaben oder dem Unterstrich beginnen und darf nur ASCII-Buchstaben, den Unterstrich und Ziffern enthalten. Mit einem regulären Ausdruck ist das auch bequem und knackig kurz machbar. Meine Funktion sieht so aus:
isVAR() { [[ ${1^^} =~ ^[A-Z_][A-Z_0-9]*$ ]] ;} Sie funktioniert auch, wie man leicht testet:
$ isVAR a ; echo $?
0
$ isVAR 7 ; echo $?
1
$ isVAR 7a ; echo $?
1
$ isVAR a7 ; echo $?
0 Allerdings funktioniert sie nur fast perfekt:
isVAR äöü ; echo $?
0
Die deutschen Umlaute sind natürlich keine ASCII-Zeichen und damit in Variablennamen ungültig. Offenbar interpretiert aber Bash den Ausdruck [A-Z] nicht als „ASCII-Zeichen A bis Z“, sondern als „alle Buchstaben gemäß der geltenden Locale“, denn ein
$ LANG= isVAR ä ; echo $?
1 beendet den Spuk. Dies ist schon die erste Überraschung. Aber es kommt noch schlimmer:
$ isVAR æØſðđŋħ ; echo $?
0 Auch das nordische ä und ö, also æ und ø, werden als zur deutschen Lokale (die bei mir eingestellt ist) gehörig behandelt, und ebenso eine Menge weiterer Zeichen. Es wird also nicht nur die deutsche Lokale benutzt, sondern eine falsche deutsche Locale, denn die nordischen Zeichen gehören natürlich nicht zum Deutschen. Mein Problem: Meine Funktion funktioniert wie gewünscht, wenn ich ihrem Aufruf ein LANG= voranstelle. Das will ich natürlich nicht bei jedem Aufruf machen, sondern lieber in die Definition der Funktion aufnehmen. Wie mache ich das?
|
|
TK87
Anmeldungsdatum: 8. Juli 2019
Beiträge: 301
Wohnort: Aachen
|
Moin, mit grep und dem Parameter --perl-regexp wird [A-Z] korrekt als ASCII-Zeichen 0x41 bis 0x5a interpretiert.
| isVar() { grep -iqP "^[A-Z_][A-Z_0-9]*$" <<<$@ ;}
|
$ isVar äöü ; echo $?
1
$ isVar æØſðđŋħ ; echo $?
1
$ isVar _abc ; echo $?
0 Gruß Thomas
|
|
shiro
Supporter
Anmeldungsdatum: 20. Juli 2020
Beiträge: 1449
|
... sondern lieber in die Definition der Funktion aufnehmen. Wie mache ich das?
Ich würde in der Funktion eine "local" Definition vornehmen, also:
$ isVAR() { local LANG= && [[ ${1^^} =~ ^[A-Z_][A-Z_0-9]*$ ]] ; }
$ isVAR ä7 ; echo $?
1
$ locale
LANG=de_DE.UTF-8
LANGUAGE=de_DE:en
LC_CTYPE="de_DE.UTF-8"
LC_NUMERIC=de_DE.UTF-8
LC_TIME=de_DE.UTF-8
LC_COLLATE="de_DE.UTF-8"
LC_MONETARY=de_DE.UTF-8
LC_MESSAGES="de_DE.UTF-8"
LC_PAPER=de_DE.UTF-8
LC_NAME=de_DE.UTF-8
LC_ADDRESS=de_DE.UTF-8
LC_TELEPHONE=de_DE.UTF-8
LC_MEASUREMENT=de_DE.UTF-8
LC_IDENTIFICATION=de_DE.UTF-8
LC_ALL=
$
|
|
TK87
Anmeldungsdatum: 8. Juli 2019
Beiträge: 301
Wohnort: Aachen
|
Ich habe mal ein wenig weiter geforscht... kB schrieb: Offenbar interpretiert aber Bash den Ausdruck [A-Z] nicht als „ASCII-Zeichen A bis Z“, sondern als „alle Buchstaben gemäß der geltenden Locale“
Nein. Bash scheint ä als Sonderform von a, ö als Sonderform von o usw. zu interpretieren. Und ä liegt für Bash genau zwischen a und b, ö genau zwischen o und p, usw.
$ [[ ö =~ [o] ]] ; echo $?
1
$ [[ ö =~ [a-o] ]] ; echo $?
1
$ [[ ö =~ [o-p] ]] ; echo $?
0
$ [[ ß =~ [s-t] ]] ; echo $?
0
$ [[ äöü =~ [ab-op-uv-z] ]] ; echo $?
1
|
|
Marc_BlackJack_Rintsch
Ehemalige
Anmeldungsdatum: 16. Juni 2006
Beiträge: 4773
Wohnort: Berlin
|
@kB: Es gibt keine „dieser Buchstabe gehört oder gehört nicht zu einer Gebiets-Locale”-Beziehung. Zeichen gehören zu einer Kodierung oder nicht, und wenn da UTF-8 steht, dann gehören alle in UTF-8 kodierbaren Zeichen zu der Locale, egal welches Gebiet. Was das Gebiet regelt, ist wie all die Zeichen die kodierbar sind, sortiert werden. Und bei [A-Z] sind zwischen A und Z alle Zeichen aus Unicode, die wenn man alle Zeichen sortiert, halt so dazwischen sind. Das dürften folgende 607 Zeichen von A bis Z sein:
AáÁàÀăắằẵẳặĂẮẰẴẲẶâấầẫẩậÂẤẦẪẨẬǎǍåǻÅǺäǟÄǞãÃȧǡȦǠąĄāĀảẢȁȀȃȂạẠḁḀẚªæǽǣÆǼǢbBḃ
ḂḅḄḇḆɓƁcCćĆĉĈčČċĊçḉÇḈƈƇdDďĎḋḊđĐḑḐḍḌḓḒḏḎɖɗƊðÐdzDzDZdžDžDŽeEéÉèÈĕĔêếềễểệÊẾỀỄỂỆ
ěĚëËẽẼėĖȩḝȨḜęĘēḗḕĒḖḔẻẺȅȄȇȆẹẸḙḘḛḚǝəɛƎƏƐfFḟḞgGǵǴğĞĝĜǧǦġĠǥǤģĢḡḠɠƓƣƢhHĥĤȟȞ
ḧḦḣḢħĦḩḨḥḤḫḪẖƕǶiIíÍìÌĭĬîÎǐǏïḯÏḮĩĨįĮīĪỉỈȉȈȋȊịỊḭḬıİijIJjJĵĴǰkKḱḰǩǨķĶḳḲḵḴƙƘ
lLĺĹľĽŀĿłŁļĻḷḹḶḸḽḼḻḺljLjLJmMḿḾṁṀṃṂnNńŃǹǸňŇñÑṅṄņŅṇṆṋṊṉṈʼnŋŊnjNjNJoOóÓòÒŏŎôốồỗổ
ộÔỐỒỖỔỘǒǑöɵȫÖƟȪőŐõṍṏȭÕṌṎȬȯȱȮȰøǿØǾǫǭǪǬōṓṑŌṒṐỏỎȍȌȏȎọỌơớờỡởợƠỚỜỠỞỢɔºƆœŒpP
ṕṔṗṖqQĸrRŕŔřŘṙṘŗŖȑȐȓȒṛṝṚṜṟṞsSśṥŚṤŝŜšṧŠṦṡṠşșŞȘṣṩṢṨſẛßtTťŤẗṫṪŧŦţțŢȚṭṬṱṰṯ
ṮuUúÚùÙŭŬûÛǔǓůŮüǘǜǚǖÜǗǛǙǕűŰũṹŨṸųŲūṻŪṺủỦȕȗụỤṳṲṷṶṵṴưứừữửựƯỨỪỮỬỰvVṽṼṿṾwWẃ
ẂẁẀŵŴẘẅẄẇẆẉẈƿǷxXẍẌẋẊyYýÝỳỲŷŶẙÿŸỹỸẏẎȳȲỷỶỵỴƴƳȝȜzZ Das Argument, dass Zeichen davon nicht “deutsch“ sind, wäre IMHO komisch, denn wenn ich beispielsweise in einem deutschsprachigen Text eine Liste mit Brücken in Europa sortiere, dann möchte ich doch die Øresund-Brücke in der Nähe von der Oderbaumbrücke sortiert haben, und nicht irgendwo hinter Z oder vor A, weil Ø nicht im deutschsprachigen Alphabet vorkommt. Es gibt ja trotzdem Worte und Eigennamen mit solchen Buchstaben die sinnvoll behandelt werden müssen.
|
|
Dakuan
Anmeldungsdatum: 2. November 2004
Beiträge: 6572
Wohnort: Hamburg
|
... darf nur ASCII-Buchstaben, den Unterstrich und Ziffern enthalten.
Könnte man da nicht zuerst prüfen, ob in dem Namen ein Byte enthalten ist, dass größer als 127 ist?
|
|
Marc_BlackJack_Rintsch
Ehemalige
Anmeldungsdatum: 16. Juni 2006
Beiträge: 4773
Wohnort: Berlin
|
Die Zeichenliste weiter oben hatte ich mit Python erstellt: Einfach alle Unicode-Zeichen gemäss aktueller locale sortiert, und dann die von A bis Z ausgegeben:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 | import sys
import textwrap
from locale import LC_ALL, setlocale, strxfrm
def main():
setlocale(LC_ALL, "")
all_characters = "".join(
sorted(map(chr, range(1, sys.maxunicode)), key=strxfrm)
)
a_to_z = all_characters[
all_characters.index("A") : all_characters.index("Z") + 1
]
print(textwrap.fill(a_to_z))
print(len(a_to_z), "characters.")
if __name__ == "__main__":
main()
|
Etwas ähnliches wollte ich dann doch noch mal mit der Bash machen: Alle Unicode-Zeichen gegen den regulären Ausdruck [A-Z] prüfen:
| #!/bin/bash
for ((i = 1; i < 1114111; i++)); do
c=$(printf %b "$(printf '\\U%08x' $i)")
[[ $c =~ [A-Z] ]] && printf %s "$c"
done
|
Das dauert aber eeeeeewig, so dass ich das abgebrochen habe, und mal geschaut habe was die Bash da macht, und ob man die Grundidee einfach schnell in C nachprogrammieren kann. Damit das keine Geduldsprobe wird. Das Handbuch von Bash sagt zum =~-Operator: An additional binary operator, =~, is available, with the same precedence as == and !=. When it is used, the string to the right of the operator is considered an extended regular expression and matched accordingly (as in regex(3)).
regex(7), wo reguläre Ausdrücke nach POSIX-Standard beschrieben werden, sagt zu „bracket expressions“: A bracket expression is a list of characters enclosed in []. […] If two characters in the list are separated by -, this is shorthand for the full range of characters between those two (inclusive) in the collating sequence, […]
Und: Ranges are very collating-sequence-dependent, and portable programs should avoid relying on them.
Hier nun das C-Programm, das alle Unicode-Zeichen gegen das Muster [A-Z] testet:
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 | #include <inttypes.h>
#include <limits.h>
#include <locale.h>
#include <regex.h>
#include <stdio.h>
#include <stdlib.h>
#include <uchar.h>
#define MAX_CHAR32 0x10ffff
int main(void)
{
setlocale(LC_ALL, "");
regex_t re;
int error_code;
if ((error_code = regcomp(&re, "[A-Z]", REG_EXTENDED | REG_NOSUB))) {
return error_code;
}
uint32_t count = 0;
char *mbs = malloc(MB_LEN_MAX);
for (char32_t c = 1; c < MAX_CHAR32; c++) {
mbstate_t state = {0};
size_t byte_count = c32rtomb(mbs, c, &state);
if (byte_count != (size_t) -1) {
mbs[byte_count] = '\0';
if (regexec(&re, mbs, 0, NULL, 0) == 0) {
printf("%s", mbs);
if (++count % 70 == 0) putchar('\n');
}
}
}
printf("\n%"PRIu32" characters.\n", count);
free(mbs);
regfree(&re);
return 0;
}
|
Testläufe:
$ LC_ALL=C ./all_letters
ABCDEFGHIJKLMNOPQRSTUVWXYZ
26 characters.
$ LC_ALL=de_DE.UTF-8 ./all_letters
ABCDEFGHIJKLMNOPQRSTUVWXYZÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝĀĂĄĆĈĊČĎĐĒĔĖĘĚĜ
ĞĠĢĤĦĨĪĬĮİIJĴĶĹĻĽĿŁŃŅŇŊŌŎŐŒŔŖŘŚŜŞŠŢŤŦŨŪŬŮŰŲŴŶŸƁƆƇƊƎƏƐƓƘƟƠƢƯƳDŽDžLJLjNJNjǍǏǑǓǕ
ǗǙǛǞǠǢǤǦǨǪǬDZDzǴǶǷǸǺǼǾȀȂȄȆȈȊȌȎȐȒȘȚȜȞȦȨȪȬȮȰȲḀḂḄḆḈḊḌḎḐḒḔḖḘḚḜḞḠḢḤḦḨḪḬḮḰḲḴḶḸ
ḺḼḾṀṂṄṆṈṊṌṎṐṒṔṖṘṚṜṞṠṢṤṦṨṪṬṮṰṲṴṶṸṺṼṾẀẂẄẆẈẊẌẎẠẢẤẦẨẪẬẮẰẲẴẶẸẺẼẾỀỂỄỆỈỊỌỎỐỒỔ
ỖỘỚỜỞỠỢỤỦỨỪỬỮỰỲỴỶỸ
298 characters.
|
|
seahawk1986
Anmeldungsdatum: 27. Oktober 2006
Beiträge: 11300
Wohnort: München
|
kB schrieb: Das will ich natürlich nicht bei jedem Aufruf machen, sondern lieber in die Definition der Funktion aufnehmen. Wie mache ich das?
Die Bash kennt lokale Variablen:
| isVAR() { local LANG=C; [[ ${1^^} =~ ^[A-Z_][A-Z_0-9]*$ ]] ;}
|
|
|
kB
Supporter, Wikiteam
(Themenstarter)
Anmeldungsdatum: 4. Oktober 2007
Beiträge: 10197
Wohnort: Münster
|
TK87 schrieb: […] mit grep und dem Parameter --perl-regexp wird [A-Z] korrekt als ASCII-Zeichen 0x41 bis 0x5a interpretiert
Danke für den Hinweis auf grep und Perl! Tatsächlich verhält sich grep genauso (oder zumindest ähnlich) wie bash, solange man Basic- oder Extended-Basic-Regexp benutzt und anders, nämlich mit dem von mir erhofften Verhalten, wenn man Perl-Regexp benutzt. Damit liegt die Ursache vermutlich in der Definition der Regexp bei POSIX, und das Verhalten beschränkt sich nicht auf die Bash.
|
|
kB
Supporter, Wikiteam
(Themenstarter)
Anmeldungsdatum: 4. Oktober 2007
Beiträge: 10197
Wohnort: Münster
|
shiro schrieb: […] in der Funktion eine "local" Definition vornehmen, also:
$ isVAR() { local LANG= && [[ ${1^^} =~ ^[A-Z_][A-Z_0-9]*$ ]] ; }
Danke! Das ist eine elegante Lösung mit den Sprachmitteln von bash, wie von mir erhofft. Ich bin aber inzwischen, auch durch diese Diskussion zur Überzeugung gelangt, dass die Verwendung von regulären Ausdrücken, insbesondere mit Bereichen wie [A-Z] in Programmen keine gute Idee ist, eben weil deren Interpretation extrem von Parametern (locale) der Umgebung abhängt, in der das Programm ausgeführt wird. Das erfordert vom Programmierer erhöhte Beachtung.
|
|
kB
Supporter, Wikiteam
(Themenstarter)
Anmeldungsdatum: 4. Oktober 2007
Beiträge: 10197
Wohnort: Münster
|
Marc_BlackJack_Rintsch schrieb: […] bei [A-Z] sind zwischen A und Z alle Zeichen aus Unicode, die wenn man [gemäß der geltenden Locale] alle Zeichen sortiert, halt so dazwischen sind.
Das mag zutreffen, was Du da schreibst, aber ein solches Verhalten ist halt nicht das, was ein naiver Programmierer erwartet. Ich benötige für meine Aufgabe eine Prüfung auf ASCII-Buchstaben von A-Z, unabhängig von der geltenden Locale. In der Bash leistet das der Glob [A-Z]. Sobald man aber diesen Ausdruck in einem regulären Ausdruck verwendet, wird er anders interpretiert, nämlich wohl so (oder ähnlich) wie Du es beschreibst. So etwas gehört bei mir in die Kategorie „Böse Falle“.
|
|
kB
Supporter, Wikiteam
(Themenstarter)
Anmeldungsdatum: 4. Oktober 2007
Beiträge: 10197
Wohnort: Münster
|
Dakuan schrieb: […] Könnte man da nicht zuerst prüfen, ob in dem Namen ein Byte enthalten ist, dass größer als 127 ist?
Auch ein guter Ansatz. Leider kenne ich aber keine knackige Formulierung, wie man mit der Bash prüft, ob ein einer Zeichenkette, die aus Sicht von bash aus Zeichen, nicht aus Bytes besteht, prüft, ob ein Byte enthalten ist, welches man auch als negative Zahl interpretieren kann. Mit Schleifen und Typumwandlungen geht das sicherlich, aber ich suche ein knackige Lösung.
|
|
kB
Supporter, Wikiteam
(Themenstarter)
Anmeldungsdatum: 4. Oktober 2007
Beiträge: 10197
Wohnort: Münster
|
Marc_BlackJack_Rintsch schrieb: Die Zeichenliste weiter oben hatte ich mit Python erstellt […]
Etwas ähnliches wollte ich dann doch noch mal mit der Bash machen […]
Das Handbuch von Bash […] Ranges are very collating-sequence-dependent, and portable programs should avoid relying on them.
Danke. Das ist ein wichtiger Satz. Man sollte ihn fett rot schreiben und dreimal unterstreichen. […] C-Programm
Kurze Zusammenfassung: Python, Bash und C verhalten sich bei Bereichen in regulären Ausdrücken bzgl. der Abhängigkeit von der geltenden Locale ähnlich, aber nicht identisch.
|
|
TK87
Anmeldungsdatum: 8. Juli 2019
Beiträge: 301
Wohnort: Aachen
|
Wie wäre es denn mit...
{{{#!code bash
isVAR() { __0-9*$ ]] ;}
}}}
Erstaunlicher Weise berücksichtigt der ":alpha:"-Posix tatsächlich nur Zeichen von A-Z (obwohl ich es gerade da anders erwartet hätte).
|
|
Dakuan
Anmeldungsdatum: 2. November 2004
Beiträge: 6572
Wohnort: Hamburg
|
Leider kenne ich aber keine knackige Formulierung, wie man mit der Bash prüft, ob ein einer Zeichenkette, ...
Ich leider auch nicht, sonst hätte ich ein Beispiel präsentieren können. Ein Denkansatz wäre, den String als Array aus Bytes zu betrachten (auch wenn das nicht so ist) und dann die Bytes einzeln zu prüfen. Jedes Byte, das zu einer UTF-8 Sequenz gehört hat das Bit 7 gesetzt (7..0) und kann hier ohne weitere Prüfung als ungültig betrachtet werden.
|