bash & sed, alle Zeichen entfernen, außer ..

Vom einfachen Programm zum fertigen Debian-Paket, Fragen rund um Programmiersprachen, Scripting und Lizenzierung.
Benutzeravatar
MartinV
Beiträge: 788
Registriert: 31.07.2015 19:38:52
Wohnort: Hyperion
Kontaktdaten:

Re: bash & sed, alle Zeichen entfernen, außer ..

Beitrag von MartinV » 17.10.2019 16:20:58

Ich habe für vergleichbare fäle eine kleine Funktion,um "langweilige" Strings zu erzeugen:

Code: Alles auswählen


unspecialstring() {             # replace special chars of $1 with -
  # Replace all characters except those described in [^a-zA-Z0-9_] with a '-'. 
  # Replace double '--' with single '-'
  # Remove leading and trailing '-'
  echo "${1:-}" | LC_ALL=C sed -e 's/[^a-zA-Z0-9_]/\-/g; 1{$s/^$/""/}; 1!s/^/"/; $!s/$/"/ ; s/\([-]\)\1\+/\1/g ; s/-$//g ; s/^-//g'
}
Sonderzeichen werden durch ein - ersetzt, aber maximal ein - in Folge.
Für Dateinamen ist es sinnvoll, auch . zu erlauben.
Die Vernunft kann einem schon leidtun. Sie verliert eigentlich immer.

Benutzeravatar
Meillo
Moderator
Beiträge: 8823
Registriert: 21.06.2005 14:55:06
Wohnort: Balmora
Kontaktdaten:

Re: bash & sed, alle Zeichen entfernen, außer ..

Beitrag von Meillo » 17.10.2019 17:43:21

MartinV hat geschrieben: ↑ zum Beitrag ↑
17.10.2019 16:20:58
Ich habe für vergleichbare fäle eine kleine Funktion,um "langweilige" Strings zu erzeugen:

Code: Alles auswählen


unspecialstring() {             # replace special chars of $1 with -
  # Replace all characters except those described in [^a-zA-Z0-9_] with a '-'. 
  # Replace double '--' with single '-'
  # Remove leading and trailing '-'
  echo "${1:-}" | LC_ALL=C sed -e 's/[^a-zA-Z0-9_]/\-/g; 1{$s/^$/""/}; 1!s/^/"/; $!s/$/"/ ; s/\([-]\)\1\+/\1/g ; s/-$//g ; s/^-//g'
}
Sonderzeichen werden durch ein - ersetzt, aber maximal ein - in Folge.
Für Dateinamen ist es sinnvoll, auch . zu erlauben.
Das ``LC_ALL=C'' ist ein wichtiges Detail!

Bei den Ersetzungen habe ich Fragen.

Diese zwei Ersetzungen:

Code: Alles auswählen

s/[^a-zA-Z0-9_]/\-/g; ...  s/\([-]\)\1\+/\1/g
koenntest du doch folgendermassen vereinen:

Code: Alles auswählen

s/[^a-zA-Z0-9_]\+/\-/g
Oder? Dann werden gleich gar keine Doppelminuse erzeugt und die bestehenden gleich mit zusammengefasst.


Dann diese Ersetzungen:

Code: Alles auswählen

1!s/^/"/; $!s/$/"/
In allen ausser der ersten Zeile wird ein Doublequote an den Zeilenanfang gesetzt. Und in allen ausser der letzten Zeile wird dein Doublequote ans Zeilenende gesetzt. Was ist der Hintergrund davon? Das sieht ein bisschen aus, wie wenn du die Zeilenumbrueche quoten willst.


Und was macht das:

Code: Alles auswählen

1{$s/^$/""/}
Das macht fuer mich wenig Sinn ... oder ich verstehe es noch nicht. Wenn ich Parser spiele, kommt das dabei raus: In der ersten Zeile (``1'') arbeite den Block (``{...}'') ab. In der letzten Zeile (``$'') <-- das macht fuer mich keinen Sinn, da wir uns in dem Block ja in der ersten Zeile befinden! ... ersetze leere Zeilen mit zwei Doublequotes.

An der Stelle verstehe ich gerade die sed-Welt nicht mehr. Stehe ich auf dem Schlauch? Ist der Befehl falsch? Was soll er denn machen?
Use ed once in a while!

Benutzeravatar
MartinV
Beiträge: 788
Registriert: 31.07.2015 19:38:52
Wohnort: Hyperion
Kontaktdaten:

Re: bash & sed, alle Zeichen entfernen, außer ..

Beitrag von MartinV » 17.10.2019 19:55:11

Das macht fuer mich wenig Sinn ...
In allen ausser der ersten Zeile wird ein Doublequote an den Zeilenanfang gesetzt. Und in allen ausser der letzten Zeile wird dein Doublequote ans Zeilenende gesetzt. Was ist der Hintergrund davon?
Du hast recht ... ein Teil des codes macht keinen Sinn. Ich wundere mich gerade selbst.
Irgendwann mal habe ich das zusammengebastelt, es funktionierte, und ich habe nicht wieder draufgeschaut.

Den Teil mit den " habe ich gerade mal entfernt, der Code tut immer noch, was er soll. Völlig überflüssig also.

Der von mir angedachte / gewollte Teil ist also nur:

Code: Alles auswählen

LC_ALL=C sed -e 's/[^a-zA-Z0-9_]/\-/g ; s/\([-]\)\1\+/\1/g ; s/-$//g ; s/^-//g'
Diese zwei Ersetzungen
koenntest du doch folgendermassen vereinen:

Code: Alles auswählen

s/[^a-zA-Z0-9_]\+/\-/g
Tatsächlich, geht! Ich verstehe zu wenig von sed, als daß ich darauf gekommen wäre.

Der Code reduziert sich damit auf:

Code: Alles auswählen

LC_ALL=C sed -e 's/[^a-zA-Z0-9_]\+/\-/g  ; s/-$//g ; s/^-//g'
Zwei Fallstricke bleiben:
- Besteht der String nur aus Sonderzeichen, wird ein leerer String / nichts zurückgegeben.
- Zeilenumbrüche bleiben erhalten.
Für meinen Zweck ("unspecial") sollte es keine Zeilenumbrüche geben.

Mit tr kann ich auch die Zeilenumbrüche entfernen:

Code: Alles auswählen

tr "\n" "-" | LC_ALL=C sed -e 's/[^a-zA-Z0-9_]\+/\-/g ; s/-$//g ; s/^-//g'
Die Vernunft kann einem schon leidtun. Sie verliert eigentlich immer.

Benutzeravatar
Meillo
Moderator
Beiträge: 8823
Registriert: 21.06.2005 14:55:06
Wohnort: Balmora
Kontaktdaten:

Re: bash & sed, alle Zeichen entfernen, außer ..

Beitrag von Meillo » 17.10.2019 21:06:15

MartinV hat geschrieben: ↑ zum Beitrag ↑
17.10.2019 19:55:11
Der Code reduziert sich damit auf:

Code: Alles auswählen

LC_ALL=C sed -e 's/[^a-zA-Z0-9_]\+/\-/g  ; s/-$//g ; s/^-//g'
Zwei Fallstricke bleiben:
- Besteht der String nur aus Sonderzeichen, wird ein leerer String / nichts zurückgegeben.
- Zeilenumbrüche bleiben erhalten.
Für meinen Zweck ("unspecial") sollte es keine Zeilenumbrüche geben.

Mit tr kann ich auch die Zeilenumbrüche entfernen:

Code: Alles auswählen

tr "\n" "-" | LC_ALL=C sed -e 's/[^a-zA-Z0-9_]\+/\-/g ; s/-$//g ; s/^-//g'
Es laesst sich noch weiter verbessern, indem man die Hauptbearbeitung von tr(1) uebernehmen laesst:

Code: Alles auswählen

LC_ALL=C tr -cs "a-zA-Z0-9_-" "-" | sed -e 's/-$// ; s/^-//'
(Wenn du Zeilenanfangs- oder Zeilenendeanker verwendest ist der `g'-Modifier sinnlos.)

Jetzt bleibt nur noch der Fall des leeren Strings. Falls du aber kein Problem mit fuehrenden und endenden Minusen haettest, gaebe es den Fall nicht mehr und zu koenntest dir zudem sed komplett sparen.

Code: Alles auswählen

LC_ALL=C tr -cs "a-zA-Z0-9_-" "-"
(Oft verwendet man sed(1) fuer Dinge, fuer die tr(1) direkt gemacht ist.)

Mit folgendem sed-Befehl werden fuehrende und endende Minuse nur dann entfernt, wenn es dazwischen mindestens ein Zeichen gibt:

Code: Alles auswählen

LC_ALL=C tr -cs "a-zA-Z0-9_" "-" | sed -e 's/^-*\(.*[^-]\)-*$/\1/'
(Die Regexp ist etwas seltsam, in der Form, dass kein ..* bzw. .\+ verwendet werden kann, sondern .*[^-], weil sonst das gierige Verhalten des Sterns das abschliessend -* gleich mit auffressen wuerde. Das ist etwas advanced ... aber fuer Interessierte umso spannender. ;-) )

Falls der String leer werden sollte, bleibt ein Minus bestehen.

Das sollte wohl das sein, was du haben willst.
Use ed once in a while!

Benutzeravatar
MartinV
Beiträge: 788
Registriert: 31.07.2015 19:38:52
Wohnort: Hyperion
Kontaktdaten:

Re: bash & sed, alle Zeichen entfernen, außer ..

Beitrag von MartinV » 17.10.2019 23:12:04

Meillo hat geschrieben: ↑ zum Beitrag ↑
17.10.2019 21:06:15
(Wenn du Zeilenanfangs- oder Zeilenendeanker verwendest ist der `g'-Modifier sinnlos.)
Guter Hinweis! Ich setze das g schon fast automatisch, weil es fast immer gebraucht wird.
Meillo hat geschrieben: ↑ zum Beitrag ↑
17.10.2019 21:06:15
Es laesst sich noch weiter verbessern, indem man die Hauptbearbeitung von tr(1) uebernehmen laesst:

Code: Alles auswählen

LC_ALL=C tr -cs "a-zA-Z0-9_-" "-" | sed -e 's/-$// ; s/^-//'
Sehr schön, danke! Ist auch besser lesbar as sed.
Die Kombination von -c und -s ist speziell. Aus der Manpage ist nicht offenkundig klar, das -s die Komplementärmenge von MENGE1 verwenden wird, wenn -c gesetzt ist.

Einen Effekt verstehe ich aber nicht. In MENGE1 hast Du auch - als zulässiges Zeichen definiert. Option -s entfernt aber nur doppelte Zeichen, die nicht in MENGE1 enthalten sind.
Das würde bedeuten, daß ein Vorkommen von --- im ursprünglichen String erhalten bleiben sollte. Es wird aber dennoch auf ein - reduziert:

Code: Alles auswählen

$ printf "aaa---bbb" | tr -cs "a-zA-Z0-9_-" "-"
aaa-bbb
Meillo hat geschrieben: ↑ zum Beitrag ↑
17.10.2019 21:06:15
Falls der String leer werden sollte, bleibt ein Minus bestehen.

Das sollte wohl das sein, was du haben willst.
Ein Leerstring ist mir im Zweifelsfall lieber. Falls die Funktion genutzt wird, um ungefährliche Dateinamen zu erzeugen, ist ein - als Ergebnis kontraproduktiv.
Zudem ist diese Schreibweise leichter lesbar:

Code: Alles auswählen

sed -e 's/-$// ; s/^-//'
Bei Deinem Vorschlag überblicke ich nicht, was da eigentlich passiert:

Code: Alles auswählen

sed -e 's/^-*\(.*[^-]\)-*$/\1/'
Etwas anderes ist mir aufgefallen: echo sendet auch einen Zeilenumbruch, der von tr in - umgewandelt wird:

Code: Alles auswählen

$ echo "abc" | tr -cs "a-zA-Z0-9_-" "-"
abc-
Das stört hier nicht, aber bei einem anderen Anwendungsfall. "echo -n" würde das vermeiden, aber ich nutze ungern echo mit Optionen, da sich echo auf verschiedenen Systemen sehr unterschiedlich verhält.

Mit printf kann ich den Zeilenumbruch (und damit ein - ) vermeiden:

Code: Alles auswählen

$ printf "abc" | tr -cs "a-zA-Z0-9_-" "-"
abc
Eine Frage dazu: Könnte printf versehentlich ein Argument bekommen, das ungewolltes Verhalten hervorruft? Also irgendetwas als Option interpretiert?

---------------------
Meine jetzige Version der Funktion für die Threadfrage:

Code: Alles auswählen

unspecialstring() {             # replace special chars of $1 with -
  # Replace all characters except those described in "a-zA-Z0-9_-" with a '-'. 
  # Replace newlines, too.
  # Avoids double '--'
  # Remove leading and trailing '-'
  # Return empty string if only special chars are given.
  LC_ALL=C printf "${1:-}" | tr -cs "a-zA-Z0-9_-" "-" | sed -e 's/-$// ; s/^-//'
}
Die Vernunft kann einem schon leidtun. Sie verliert eigentlich immer.

Benutzeravatar
Meillo
Moderator
Beiträge: 8823
Registriert: 21.06.2005 14:55:06
Wohnort: Balmora
Kontaktdaten:

Re: bash & sed, alle Zeichen entfernen, außer ..

Beitrag von Meillo » 18.10.2019 01:18:41

MartinV hat geschrieben: ↑ zum Beitrag ↑
17.10.2019 23:12:04
Die Kombination von -c und -s ist speziell. Aus der Manpage ist nicht offenkundig klar, das -s die Komplementärmenge von MENGE1 verwenden wird, wenn -c gesetzt ist.
Ja, die Manpage koennte hier etwas genauer sein. Fuer mich war es aber einleuchtend, weil es nur so Sinn macht.
Einen Effekt verstehe ich aber nicht. In MENGE1 hast Du auch - als zulässiges Zeichen definiert. Option -s entfernt aber nur doppelte Zeichen, die nicht in MENGE1 enthalten sind.
Das würde bedeuten, daß ein Vorkommen von --- im ursprünglichen String erhalten bleiben sollte. Es wird aber dennoch auf ein - reduziert:

Code: Alles auswählen

$ printf "aaa---bbb" | tr -cs "a-zA-Z0-9_-" "-"
aaa-bbb
Sehr gut aufgepasst! Mir ist das auch (beim Durchdenken) aufgefallen. Daraufhin habe ich es getestet: Egal ob das Minus in der ersten Menge ist, es wird immer gesqueezet. Das sieht mir nach einem Bug aus. Naeher recherchiert (neueste Version von tr, Bugtracker) habe ich aber noch nicht. [siehe unten]
Bei Deinem Vorschlag überblicke ich nicht, was da eigentlich passiert:

Code: Alles auswählen

sed -e 's/^-*\(.*[^-]\)-*$/\1/'
Ich ersetze eine komplette Zeile, die mit optionalen Minusen anfaengt und mit optionalen Minusen aufhoert, durch das was zwischen diesen Minusen steht. Eigentlich sollten der erste und letzte Stern Fragezeichen sein, weil ich hier nur auf null-oder-eins pruefen will, aber da `tr -s' maximal eines erzeugt, macht das keinen Unterschied.

Wenn ich in der Klammer ..* (als Aequivalent zu .\+, aber portabel) schreiben wuerde, dann wuerde das abschliessende Minus immer in der Klammer landen, weil POSIX Regexp gierig sind. Die Klammer wuerde also die komplette Zeile auffressen und das optionale Minus am Ende wuerde stets leer ausgehen. (Ungreedy-Modifier, wie bei PCREs, gibt es bei POSIX nicht.) Also sage ich, dass in der Klammer beliebig viele optionale Zeichen stehen koennen, gefolgt von einem noetigen Zeichen, das kein Minus ist. Falls am Ende ein Minus sein sollte, landet es dadurch hinter der Klammer. Falls die Zeile nur aus Minusen bestehen sollte -- dank `tr -s' ist es maximal eines -- wird nichts ersetzt, weil die Regexp nicht passt.

Ist das klar geworden, oder muss ich etwas davon noch genauer erklaeren?
Etwas anderes ist mir aufgefallen: echo sendet auch einen Zeilenumbruch, der von tr in - umgewandelt wird:

Code: Alles auswählen

$ echo "abc" | tr -cs "a-zA-Z0-9_-" "-"
abc-
Ja. Ich bin davon ausgegangen, dass die Funktion in Scripten auf Strings angewendet werden soll, und dass die keine Newlines am Ende haben. Da du die Minuse eh wegfiltern willst, eruebrigt sich dieses Thema aber.
"echo -n" würde das vermeiden, aber ich nutze ungern echo mit Optionen, da sich echo auf verschiedenen Systemen sehr unterschiedlich verhält.

Mit printf kann ich den Zeilenumbruch (und damit ein - ) vermeiden:

Code: Alles auswählen

$ printf "abc" | tr -cs "a-zA-Z0-9_-" "-"
abc
Eine Frage dazu: Könnte printf versehentlich ein Argument bekommen, das ungewolltes Verhalten hervorruft? Also irgendetwas als Option interpretiert?
printf(1) ist hier auch die bessere Wahl als `echo -n', aber, wie du schon selber merkst, ist

Code: Alles auswählen

printf "$var"
nicht vorhersagbar. Darum verwende:

Code: Alles auswählen

printf %s "$var"
... und das Verhalten ist eindeutig definiert.
---------------------
Meine jetzige Version der Funktion für die Threadfrage:

Code: Alles auswählen

unspecialstring() {             # replace special chars of $1 with -
  # Replace all characters except those described in "a-zA-Z0-9_-" with a '-'. 
  # Replace newlines, too.
  # Avoids double '--'
  # Remove leading and trailing '-'
  # Return empty string if only special chars are given.
  LC_ALL=C printf "${1:-}" | tr -cs "a-zA-Z0-9_-" "-" | sed -e 's/-$// ; s/^-//'
}
Wie gesagt, du brauchst noch ein `%s' beim printf(1), oder du verwendest einfach `echo', weil das Minus durch das Newline am Ende ja wieder entfernt wird.



Nachtrag: Nun habe ich mir `-s' doch etwas genauer angeschaut. Hier aus der Manpage der Heirloom-Tools:

Code: Alles auswählen

     -s
          squeezes all strings of repeated output characters that
          are in string2 to single characters.
Das beschreibt das von uns betrachtete Verhalten korrekt, da es sich die Menge 2 (``string2'') und nicht auf die Menge 1 bezieht. Nach der Beschreibung ist es folglich egal, ob das Minus in Menge 1 vorkommt oder nicht.


Und hier aus der POSIX-Manpage:

Code: Alles auswählen

     -s
            Replace instances of repeated characters with a  sin-
            gle  character, as described in the EXTENDED DESCRIP-
            TION section.

[...]

     When the -s option is  specified,  after  any  deletions  or
     translations  have  taken  place,  repeated sequences of the
     same character shall be replaced by one  occurrence  of  the
     same character, if the character is found in the array spec-
     ified by the last operand.
Auch das bezieht sich auf die Menge 2 (``last operand'').


Fazit: Die GNU-Manpage (tr-8.14) sieht fuer mich falsch aus. Sie entspricht weder POSIX noch ihrer eigenen Implementierung.
Use ed once in a while!

Benutzeravatar
MartinV
Beiträge: 788
Registriert: 31.07.2015 19:38:52
Wohnort: Hyperion
Kontaktdaten:

Re: bash & sed, alle Zeichen entfernen, außer ..

Beitrag von MartinV » 19.10.2019 17:29:36

Meillo hat geschrieben: ↑ zum Beitrag ↑
18.10.2019 01:18:41
Ist das klar geworden, oder muss ich etwas davon noch genauer erklaeren?
Danke für Deine Bereitschaft, noch weiter zu erklären!
Mir raucht schon der Kopf, im Augenblick will ich da nicht noch weiter reindenken.
Meillo hat geschrieben: ↑ zum Beitrag ↑
18.10.2019 01:18:41
Ja. Ich bin davon ausgegangen, dass die Funktion in Scripten auf Strings angewendet werden soll, und dass die keine Newlines am Ende haben. Da du die Minuse eh wegfiltern willst, eruebrigt sich dieses Thema aber.
Die Eingabestrings haben im Regelfall (zumindest bei mir) keinen Zeilenumbruch. Die Newline stammt nur von echo.
In einer ähnlichen Funktion ist dieser Effekt sehr störend, da nehme ich jetzt 'printf %s'.
Meillo hat geschrieben: ↑ zum Beitrag ↑
18.10.2019 01:18:41
printf %s "$var"
Danke! Das war es, was ich suchte. Ich hatte noch so eine vage Erinnerung an so etwas.
Meillo hat geschrieben: ↑ zum Beitrag ↑
18.10.2019 01:18:41
Nachtrag: Nun habe ich mir `-s' doch etwas genauer angeschaut.
Interessante Zitate, das macht einiges klarer!
In der GNU-Manpage ist weiter unten auch eine richtige Erklärung enthalten. Die erste Erklärung, nach der sich -s immer auf MENGE1 bezieht, ist eindeutig falsch:

Code: Alles auswählen

 -s, --squeeze-repeats
              Jede Sequenz mit sich wiederholenden Zeichen aus MENGE1 durch ein einziges Vorkommen dieses Zeichens ersetzen
              
[...]

-s benutzt die zuletzt angegebene MENGE und erfolgt nach dem Umwandeln oder Löschen.
Die GNU-Manpage widerspricht sich also selbst.
----------------------------------

Die wohl abschließende Version der Funktion für den Thread:

Code: Alles auswählen

unspecialstring() {             # replace special chars of $1 with -
  # Replace all characters except those described in "a-zA-Z0-9_" with a '-'. 
  # Replace newlines, too.
  # Remove leading and trailing '-'
  # Avoid double '--'
  # Return empty string if only special chars are given.
  LC_ALL=C printf %s "${1:-}" | tr -cs "a-zA-Z0-9_" "-" | sed -e 's/^-// ; s/-$//'
}
Die Vernunft kann einem schon leidtun. Sie verliert eigentlich immer.

Benutzeravatar
Meillo
Moderator
Beiträge: 8823
Registriert: 21.06.2005 14:55:06
Wohnort: Balmora
Kontaktdaten:

Re: bash & sed, alle Zeichen entfernen, außer ..

Beitrag von Meillo » 19.10.2019 21:06:35

MartinV hat geschrieben: ↑ zum Beitrag ↑
19.10.2019 17:29:36
Meillo hat geschrieben: ↑ zum Beitrag ↑
18.10.2019 01:18:41
Ist das klar geworden, oder muss ich etwas davon noch genauer erklaeren?
Danke für Deine Bereitschaft, noch weiter zu erklären!
Mir raucht schon der Kopf, im Augenblick will ich da nicht noch weiter reindenken.
:-D

Die GNU-Manpage widerspricht sich also selbst.
;-)

Wenn es in der neuesten Version noch immer so ist, ist das einen Bugreport wert. (Wer will kann hier ganz schnell die Gelegenheit nutzen. ;-) )

Die wohl abschließende Version der Funktion für den Thread:

Code: Alles auswählen

unspecialstring() {             # replace special chars of $1 with -
  # Replace all characters except those described in "a-zA-Z0-9_" with a '-'. 
  # Replace newlines, too.
  # Remove leading and trailing '-'
  # Avoid double '--'
  # Return empty string if only special chars are given.
  LC_ALL=C printf %s "${1:-}" | tr -cs "a-zA-Z0-9_" "-" | sed -e 's/^-// ; s/-$//'
}
[/quote]
... nicht ganz. :-P

`LC_ALL=C' muss vor den Befehl den du beeinflussen willst und das ist `tr', denn siehe:
[code]
B-) LC_ALL=C printf 'foo\nFoo\n' | sort  # en_US.UTF-8
foo
Foo

B-) printf 'foo\nFoo\n' | LC_ALL=C sort  # C, d.h. ASCIIbetical
Foo
foo


Btw: Ich finde das alles ein super spannendes Thema. Herzlichen Dank fuer deine Beitraege, die mich erst dazu gebracht haben, genauer hinzuschauen. Das hat mir eine grosse Freude bereitet ... und tut es noch immer. :THX:
Use ed once in a while!

Benutzeravatar
MartinV
Beiträge: 788
Registriert: 31.07.2015 19:38:52
Wohnort: Hyperion
Kontaktdaten:

Re: bash & sed, alle Zeichen entfernen, außer ..

Beitrag von MartinV » 19.10.2019 21:31:52

Meillo hat geschrieben: ↑ zum Beitrag ↑
19.10.2019 21:06:35
... nicht ganz. :-P `LC_ALL=C' muss vor den Befehl den du beeinflussen willst und das ist `tr'
Argh! Guter Hinweis. Ich dachte, das geht durch die Pipe, aber diese Schreibweise soll die Variable ja nur an einen Befehl übergeben. Es gibt wirklich viele Fallstricke.
Also jetzt:

Code: Alles auswählen

unspecialstring() {             # replace special chars of $1 with -
  # Replace all characters except those described in "a-zA-Z0-9_" with a '-'. 
  # Replace newlines, too.
  # Remove leading and trailing '-'
  # Avoid double '--'
  # Return empty string if only special chars are given.
  printf %s "${1:-}" | LC_ALL=C tr -cs "a-zA-Z0-9_" "-" | sed -e 's/^-// ; s/-$//'
}
Eine ganz ähnliche Funktion nutze ich, um den Inhalt von Umgebungsvariablen zu überprüfen:

Code: Alles auswählen

check_envvar() {                # allow only chars in string $1 that can be exspected in environment variables
  # Allows only chars in "a-zA-Z0-9_:/.,@=-"
  # Option -w allows whitespace, too. Can be needed for PATH.
  # Char * as in LS_COLORS is not allowed to avoid abuse.
  # Replaces forbidden chars with X and returns 1
  # Returns 0 if no change occured.
  # Echoes result.
  local Newvar Space=
  
  case "${1:-}" in
    -w) Space=" " ; shift ;;
  esac
  
  Newvar="$(printf %s "${1:-}" | LC_ALL=C tr -c "a-zA-Z0-9_:/.,@=${Space}-" "X" )"
  
  printf %s "$Newvar"
  printf "\n"
  
  [ "$Newvar" = "${1:-}" ] && return 0
  
  echo "check_envvar(): Input string has been changed. Result: $Newvar" >&2
  return 1
}
Bei dieser Funktion muß es wirklich printf und nicht echo sein, weil der Zeilenumbruch von echo das Ergebnis zerstört.
Die Vernunft kann einem schon leidtun. Sie verliert eigentlich immer.

Exxter
Beiträge: 383
Registriert: 10.01.2003 00:15:15
Lizenz eigener Beiträge: GNU General Public License

Re: bash & sed, alle Zeichen entfernen, außer ..

Beitrag von Exxter » 30.04.2020 10:46:12

Hallo,

bitte entschuldigt, dass ich den Thread nochmal hochhole. Meine Lösung war nicht ausreichend, es kamen wieder Sonderzeichen durch. Nun wollte ich eure fleißig ausgearbeitete Lösung (Hochachtung, ich verstehe nicht mal die Hälfte) in mein Script einbauen, aber ich verstehe noch nicht wie. Ich habe eine Textdatei mit vielen Sonderzeichen erstellt und filter diese:

Code: Alles auswählen

root@cloud:/tmp/muelltest$ cat test.txt | printf %s "${1:-}" | LC_ALL=C tr -cs "a-zA-Z0-9_" "-" | sed -e 's/^-// ; s/-$//'
root@cloud:/tmp/muelltest$
Da kommt aber nichts durch? Folgendes funktioniert auf den ersten Blick:

Code: Alles auswählen

root@cloud:/tmp/muelltest$ cat test.txt | tr -dc [:alnum:]-_
---____abcdjnJNbzwmitbzwmitU00D8bzwmitbzwmitbzwmitbzwmitbzwmitbzwmitbzwmitbzwmit--__bzwmitbzwmitbzwmitibzwmitoooABCD-___root@cloud:/tmp/muelltest$
root@cloud:/tmp/muelltest$
Ich möchte den Filter in folgendes Script in der kommentar-Zeile einbauen (hier noch mit der bisherigen Lösung die nicht ausreichend ist):

Code: Alles auswählen

dateien=$(find . -iname '*.jpg' -size +3k -type f | sed 's/.\///')
for f in $dateien
do
	kommentar=$(exiftool -v $f | grep Comment | sed -e "s/  | | 18) UserComment = GCM_TAG//" | tr -d [:blank:] | tr -d [:space:] | tr -d [:cntrl:] | tr -d [:graph:]  | sed -z 's/[^0-9a-zA-Z\_\-]*//g')
	seriennummer=$(echo "$kommentar" | grep -o '[0-9]*')
	abteilungundserial=$(echo "$kommentar" | sed -e "s/$seriennummer//")
	abteilung=$(echo "$abteilungundserial" | sed -e "s/SERIAL//I")
	datum=$(exiftool -v $f | grep DateTimeOriginal | sed -e "s/  | | 5)  DateTimeOriginal = //")
	datumformat=$(echo "$datum" | sed -e 's/:/-/g' -e 's/ /-/g')
	
	dateiname=$(echo "$serial"-"$seriennummer"-"$abteilung"-"$datumformat")
	
done
Was mache ich falsch, dass bei eurer Lösung nichts durch kommt?
Und wie binde ich das in mein Script korrekt ein?
Und wäre "tr -dc [:alnum:]-_" nicht auch eine Lösung? Soweit ich das verstanden habe bedeutet das -c: "filtere alles weg, außer das Angegebene (Buchstaben, Zahlen, - und _)"?
Zuletzt geändert von Exxter am 30.04.2020 11:19:31, insgesamt 2-mal geändert.

Benutzeravatar
Meillo
Moderator
Beiträge: 8823
Registriert: 21.06.2005 14:55:06
Wohnort: Balmora
Kontaktdaten:

Re: bash & sed, alle Zeichen entfernen, außer ..

Beitrag von Meillo » 30.04.2020 11:02:17

Exxter hat geschrieben: ↑ zum Beitrag ↑
30.04.2020 10:46:12
Und wäre "tr -dc [:alnum:]-_" nicht auch eine Lösung? Soweit ich das verstanden habe bedeutet das -c: "filtere alles weg, außer das angegebene (Buchstaben, Zahlen, - und _)"?
Deine Interpraetation des Befehls ist falsch. Das Minuszeichen steht nur am Anfang oder am Ende fuer sich selbst, sonst gibt es einen Bereich an.

Korrekt bedeutet dein Ausdruck: ... alles ausser alphanumerische Zeichen und allem zwischen dem zufaellig letzten Zeichen von [:alnum:] und dem Unterstrich. ;-)

So rum wird's funktionieren:

Code: Alles auswählen

tr -dc [:alnum:]_-
Use ed once in a while!

Exxter
Beiträge: 383
Registriert: 10.01.2003 00:15:15
Lizenz eigener Beiträge: GNU General Public License

Re: bash & sed, alle Zeichen entfernen, außer ..

Beitrag von Exxter » 30.04.2020 11:15:35

:facepalm: danke dir! Aber ist das eine gleichwertige Lösung zu eurer oder auch nur die halbe Wahrheit?

Benutzeravatar
Meillo
Moderator
Beiträge: 8823
Registriert: 21.06.2005 14:55:06
Wohnort: Balmora
Kontaktdaten:

Re: bash & sed, alle Zeichen entfernen, außer ..

Beitrag von Meillo » 30.04.2020 15:14:31

Exxter hat geschrieben: ↑ zum Beitrag ↑
30.04.2020 10:46:12
Ich habe eine Textdatei mit vielen Sonderzeichen erstellt und filter diese:

Code: Alles auswählen

root@cloud:/tmp/muelltest$ cat test.txt | printf %s "${1:-}" | LC_ALL=C tr -cs "a-zA-Z0-9_" "-" | sed -e 's/^-// ; s/-$//'
root@cloud:/tmp/muelltest$
Da kommt aber nichts durch?
Das liegt daran, dass das `printf' hier nicht hingehoert. ;-)

Das `cat' gibt den Inhalt von `test.txt' auf stdout aus, das wandert into `printf', das stdin aber gar nicht liest, sondern nur das erste Argument des Scripts ausgibt. Du musst das `cat' *statt* dem `printf' in die Pipeline setzen.

Ich möchte den Filter in folgendes Script in der kommentar-Zeile einbauen
Es wird uebersichtlicher wenn du dafuer eine Funktion verwendest. Etwa so:

Code: Alles auswählen

simplifystring() {
	tr ... | sed ... # hier die Befehlszeile die den Input umbaut, *ohne* printf/echo/cat davor
}

...
for f in $dateien
do
	kommentar="$(exiftool ... | simplifystring)"
	
...
(Eine Shellfunktion verhaelt sich wie ein Shellscript oder ein sonstiger Befehl.)

(hier noch mit der bisherigen Lösung die nicht ausreichend ist):

Code: Alles auswählen

dateien=$(find . -iname '*.jpg' -size +3k -type f | sed 's/.\///')
for f in $dateien
do
	kommentar=$(exiftool -v $f | grep Comment | sed -e "s/  | | 18) UserComment = GCM_TAG//" | tr -d [:blank:] | tr -d [:space:] | tr -d [:cntrl:] | tr -d [:graph:]  | sed -z 's/[^0-9a-zA-Z\_\-]*//g')
	seriennummer=$(echo "$kommentar" | grep -o '[0-9]*')
	abteilungundserial=$(echo "$kommentar" | sed -e "s/$seriennummer//")
	abteilung=$(echo "$abteilungundserial" | sed -e "s/SERIAL//I")
	datum=$(exiftool -v $f | grep DateTimeOriginal | sed -e "s/  | | 5)  DateTimeOriginal = //")
	datumformat=$(echo "$datum" | sed -e 's/:/-/g' -e 's/ /-/g')
	
	dateiname=$(echo "$serial"-"$seriennummer"-"$abteilung"-"$datumformat")
	
done
Wo kommt denn das `-z' bei sed her? Was macht das ueberhaupt? (NULL-terminierten Input verarbeiten?!) Ich sehe nicht wozu das noetig waere, moeglicherweise ist das ein Problem.

... wobei `tr -d [:graph:]' natuerlich auch alle grafischen, also druckbaren Zeichen entfernt. :-D


Soviel mal an schneller Analyse. Versuche das aufzugreifen, dann poste gerne wieder.
Use ed once in a while!

Antworten