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

Vom einfachen Programm zum fertigen Debian-Paket, Fragen rund um Programmiersprachen, Scripting und Lizenzierung.
Exxter
Beiträge: 385
Registriert: 10.01.2003 00:15:15
Lizenz eigener Beiträge: GNU General Public License

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

Beitrag von Exxter » 16.10.2019 09:50:03

Hallo,

ich habe ein Bash-Script, welches aus exif-Daten von Fotos Dateinamen für diese Fotos erzeugt und die Fotos umbenennt. Leider kommen in den exif-Daten manchmal Sonderzeichen mit, zuletzt zB. ein Enter-Zeichen:

Code: Alles auswählen

root@server /home/fotos # detox -rv SERIAL-0719
04-2019-07-08-14-09-10.jpg  04-2019-07-08-14-09-19.jpg  4-2019-07-08-14-09-31.jpg   SERIAL-0719
root@server /home/fotos # 
Ich habe schon das Tool detox gefunden, welches mir die Dateien immerhin auflistet (aber nicht repariert). Aber wie man oben sehen kann, wäre es besser solche Zeichen per sed zu filtern - bzw. andersrum, alles außer Buchstaben, Zahlen und Bindestriche fliegt raus. Ich filtere bereits per sed bestimmte Strings raus:

Code: Alles auswählen

# exifdaten auslesen, filtern und unnoetiges entfernen
kommentar=$(exiftool -v $f | grep Comment | sed -e "s/  | | 18) UserComment = GCM_TAG//")
Ich würde ein weiteres sed dahinter hängen, doch wie definiere ich den Filter dafür? Die original Dateien von oben sind nicht mehr vorhanden und die Dateien die ein Enter enthalten sind kaputt. Problem ist, ich weiß nicht wie ich das testen kann.

Jemand eine Idee?
Zuletzt geändert von Exxter am 16.10.2019 13:29:08, insgesamt 2-mal geändert.

tobo
Beiträge: 2336
Registriert: 10.12.2008 10:51:41

Re: bash & sed, Sonderzeichen entfernen

Beitrag von tobo » 16.10.2019 10:10:58

Das ist die falsche Ebene für sed. Zum Umbenennen würde ich sowas in der Art machen und bei Gefallen den Simualtionschalter (-n) von rename entfernen.

Code: Alles auswählen

$ find . -type f -name "*
*" -exec rename -n 's/\n/_/g' {} \;

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

Re: bash & sed, Sonderzeichen entfernen

Beitrag von Exxter » 16.10.2019 10:31:01

Hallo tobo,

ich glaube ich habe mich falsch ausgedrückt: der Dateiname wird erst erzeugt aus Daten, die aus den exif-Daten einer Bilddatei kommen. Wenn ich erst danach hingehe und die Dateien umbenenne ist das meiner Meinung nach zu spät - wieso filter ich das nicht gleich, nachdem ich die exif-Daten ausgelesen habe? Hier mal ein Ausschnitt aus dem Script:

Code: Alles auswählen

# Dateien aelter als 5 Minuten suchen und ./ vor jeder Datei entfernen
# Dateien kleiner als 3KB werden ignoriert, die sind dann eh kaputt
dateien=$(find . -iname '*.jpg' -size +3k -type f -mmin +5 | sed 's/.\///')

for f in $dateien
do

#######################################
# Comment-Feld aus Exif
#######################################

        # exifdaten auslesen, filtern und unnoetiges entfernen
        kommentar=$(exiftool -v $f | grep Comment | sed -e "s/  | | 18) UserComment = GCM_TAG//")

        # aus dem kommentar (vom Comment-Feld aus Exif) die Seriennummer filtern
        seriennummer=$(echo "$kommentar" | grep -o '[0-9]*')

        # die Abteilung (zb. VERS) und die SERIAL filtern
        abteilungundserial=$(echo "$kommentar" | sed -e "s/$seriennummer//")

        # nur die Abteilung
        abteilung=$(echo "$abteilungundserial" | sed -e "s/SERIAL//I")

        # den Wert "Serial" filtern (manchmal gross und manchmal klein geschrieben)
        # je nach Kameraeinstellung
        #serial=$(echo "$abteilungundserial" | sed -e "s/$abteilung//")
        serial=SERIAL
aus den Variablen baue ich mir dann einen neuen Dateiname und benenne die Datei um.

Hmm:

Code: Alles auswählen

root@sa /tmp/muell # find . -type f -name "test*" -exec rename -n 's/\n/_/g' {} \;
find: ‘rename’: Datei oder Verzeichnis nicht gefunden
find: ‘rename’: Datei oder Verzeichnis nicht gefunden
root@sa /tmp/muell # man rename
root@sa /tmp/muell # ren
rename.ul  renice
root@sa /tmp/muell # man rename.ul
root@sa /tmp/muell # find . -type f -name "test*" -exec rename.ul -n 's/\n/_/g' {} \;
rename.ul: Nicht genug Argumente
Rufen Sie „rename.ul --help“ auf, um weitere Informationen zu erhalten.
rename.ul: Nicht genug Argumente
Rufen Sie „rename.ul --help“ auf, um weitere Informationen zu erhalten.
root@sa /tmp/muell #
root@sa /tmp/muell # ls
test.sh  test©test367.jpg
root@sa /tmp/muell #
Und es geht auch nicht nur um das Enter-Zeichen, es geht darum, zukünftig keine kaputten Dateinamen mehr zu haben, egal welches Sonderzeichen da kommt, es soll alles raus bis auf Buchstaben, Zahlen, Unter- und Bindestriche.

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

Re: bash & sed, Sonderzeichen entfernen

Beitrag von Exxter » 16.10.2019 10:40:59

Ich habe mir eine Testdatei gebastelt mit einem © im Name:

Code: Alles auswählen

#!/bin/bash

datei=$(ls test©test367.jpg)

echo "$datei"

sauber=$(echo "$datei" | grep -o '[a-zA-Z0-9-]')

echo "$sauber"

exit 0
Das klappt theoretisch,aber die Ausgabe ist:

Code: Alles auswählen

root@sa /tmp/muell # ./test.sh
test©test367.jpg
t
e
s
t
t
e
s
t
3
6
7
j
p
g
root@sa /tmp/muell #
Warum kommt bei der zweiten Ausgabe jeder Buchstabe in einer extra Zeile?

Benutzeravatar
smutbert
Beiträge: 8342
Registriert: 24.07.2011 13:27:39
Wohnort: Graz

Re: bash & sed, Sonderzeichen entfernen

Beitrag von smutbert » 16.10.2019 10:55:55

das macht die Option o von grep:
man grep hat geschrieben: -o, --only-matching
Print only the matched (non-empty) parts of a matching line, with each such part on
a separate output line.

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

Re: bash & sed, Sonderzeichen entfernen

Beitrag von Exxter » 16.10.2019 11:11:46

Ah, danke, ja, das war das. Aber:

Code: Alles auswählen

root@sa /tmp/muell # echo test©test367.jpg|egrep [[:alpha:]]
test©test367.jpg
root@sa /tmp/muell #
Müsste da nicht das © die Zahlen und der . entfernt werden?

Auch das funktioniert nicht:

Code: Alles auswählen

root@sa /tmp/muell # echo "test©test367.jpg"|sed s/^[^0-9]*//
367.jpg
root@sa /tmp/muell #
Normal sollte auch alles nach dem . weg sein?

Benutzeravatar
smutbert
Beiträge: 8342
Registriert: 24.07.2011 13:27:39
Wohnort: Graz

Re: bash & sed, Sonderzeichen entfernen

Beitrag von smutbert » 16.10.2019 11:43:32

Ohne die Option o gibt grep die gesamte Zeile aus, egrep '[[:alpha:]]' also alle Zeilen, in denen Buchstaben vorkommen.

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

Re: bash & sed, Sonderzeichen entfernen

Beitrag von Exxter » 16.10.2019 11:50:33

Du hast Recht, grep ist ja zeilenbasiert.

Ich komme nicht weiter, hat denn keiner eine Idee, wie ich alle Zeichen außer Buchstaben, Zahlen und Bindestriche aus einer Variable filtern kann?

Ah ich glaub ich habs:

Code: Alles auswählen

root@sa /tmp/muell # echo 'Ein Test Bla test. 1234567-89 bux 2nd nu_mmer ist 34. 3V3k4W5' | sed 's/[^0-9a-zA-Z\_\-]*//g'
EinTestBlatest1234567-89bux2ndnu_mmerist343V3k4W5
root@sa /tmp/muell #
Sollte so klappen. Jetzt muss ich nur noch finden, wie ich da ein richtiges Sonderzeichen reinbekomme.

debianoli
Beiträge: 4152
Registriert: 07.11.2007 13:58:49
Lizenz eigener Beiträge: MIT Lizenz

Re: bash & sed, alles entfernen, außer ..

Beitrag von debianoli » 16.10.2019 12:08:35

Biegt detox das nicht alles wieder richtig hin? Dann wäre es egal, was für Schrott in den Namen rutscht.

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

Re: bash & sed, alles entfernen, außer ..

Beitrag von Exxter » 16.10.2019 12:18:29

Leider nein. Ich habe in dem Ordner, in dem die kaputten Dateien liegen, ein
detox -v .
gemacht, die Dateien sind immer noch kaputt.

Leider funktioniert meine Lösung auch nicht:

Code: Alles auswählen

#!/bin/bash

datei=$(echo 'Ein Test Bla test. 1234567-89 bux
 2nd nu_mmer ist 34. 3V3k4W5')

echo "$datei"

sauber=$(echo "$datei" | sed 's/[^0-9a-zA-Z\_\-]*//g')

echo "$sauber"

exit 0
ergibt:

Code: Alles auswählen

root@sa /tmp/muell # ./testscript.sh
Ein Test Bla test. 1234567-89 bux
 2nd nu_mmer ist 34. 3V3k4W5
EinTestBlatest1234567-89bux
2ndnu_mmerist343V3k4W5
root@sa /tmp/muell #
Ein Zeilenumbruch wird wohl nicht als Zeichen erkannt.

Edit: wieder ein Stück weiter. Sed mit der Option -z:
-z, --null-data

separate lines by NUL characters
Damit wird der Zeilenumbruch entfernt. Aber ist das wirklich richtig? Ich muss gestehen, ich lese zum ersten mal von diesem Zeichen: https://de.wikipedia.org/wiki/Nullzeichen

debianoli
Beiträge: 4152
Registriert: 07.11.2007 13:58:49
Lizenz eigener Beiträge: MIT Lizenz

Re: bash & sed, alles entfernen, außer ..

Beitrag von debianoli » 16.10.2019 12:32:34

Exxter hat geschrieben: ↑ zum Beitrag ↑
16.10.2019 12:18:29
Leider nein. Ich habe in dem Ordner, in dem die kaputten Dateien liegen, ein
detox -v .
gemacht, die Dateien sind immer noch kaputt.
Geht auch nicht. Mach mal

Code: Alles auswählen

detox -v *

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

Re: bash & sed, alles entfernen, außer ..

Beitrag von Exxter » 16.10.2019 13:11:59

Du hast Recht, aber klappt auch nicht mit *:

Code: Alles auswählen

Scanning: SERIAL-1193238-HZR-2019-08-20-07-46-24.jpg
Scanning: SERIAL-1201561666
2019-07-29-08-54-56.jpg
SERIAL-1201561666
2019-07-29-08-54-56.jpg -> SERIAL-1201561666
019-07-29-08-54-56.jpg
Ausgabe ls -al

Code: Alles auswählen

-rwxr-x--- 1 hzrfotos hzrfotos 2597791 Sep  3 20:00 SERIAL-1193238-HZR-2019-08-20-07-46-24.jpg
-rwxr-x--- 1 hzrfotos hzrfotos 2557002 Aug  5 20:00 SERIAL-1201561666
019-07-29-08-54-56.jpg
-rwxr-x--- 1 hzrfotos hzrfotos 2606061 Aug  5 20:00 SERIAL-1201561666
019-07-29-08-55-11.jpg

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

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

Beitrag von Exxter » 16.10.2019 14:09:22

Hallo,

so, ich denke ich habs. Ist zwar etwas heftig, aber so bin ich mir fast sicher, dass nie irgendwelche Sonderzeichen in die Dateinamen kommen:

Code: Alles auswählen

kommentar=$(exiftool -v $f | grep Comment | sed -e "s/  | | 18) UserComment = GCM_TAG//" | tr -d [:cntrl:] | tr -d [:blank:] | sed -z 's/[^0-9a-zA-Z\_\-]*//g')
Vielen Dank für eure Antworten!

DeletedUserReAsG

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

Beitrag von DeletedUserReAsG » 16.10.2019 20:49:11

OT, aber möglicherweise ist’s nicht so bewusst: derlei Spielereien sollte man nicht als Root durchführen. Wenn dabei was schiefgeht, reißt’s u.U. gleich das ganze System mit in den Abgrund. Klassiker wäre das Leerzeichen an der falschen Stelle, vielleicht, weil falsch gequotet, vielleicht, weil nicht escaped, in Kombination mit rm.

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

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

Beitrag von Meillo » 17.10.2019 09:57:08

Nur nebenbei: Fuer die Entfernungen und Ersetzungen hier waere tr(1) zumeist besser geeignet als sed(1).
Use ed once in a while!

Benutzeravatar
MartinV
Beiträge: 790
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: 9225
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: 790
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: 9225
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: 790
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: 9225
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: 790
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: 9225
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: 790
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: 385
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.

Antworten