# Sed - letztes Vorkommen eines Strings



## OnePixel (24. Februar 2010)

Hallo @All,

ich habe ein Problem mit sed und hoffe hier auf Hilfe bzw. Lösung meines Problems ;-)

zu meinem Probem:
Ich möchte in der "resolv.conf" einen nameserver hinzufügen, der unter dem letzten nameserver-EIntrag stehen soll. Ich wollte also nun mit sed 
den letzten nameserver am Zeilenanfang suchen und unter dem eine Leerzeile einfügen und in einem weiteren sed Befehl den neuen nameserver eintragen. 

Der zweite Befehl funktioniert auch jedoch kann ich mit sed nicht den ersten umsetzen (den letzten nameserver am Zeilenanfang suchen und eine Leerzeile unter diesem einfügen.

Der zweite Befehl sieht bei mir wie folgt aus:
 cat /etc/resolv.conf | sed "s/^$/nameserver 999.999.99.9/" > /etc/resolv.conf

Die resolv.conf besitzt bei mir folgende Syntax:

domain meineDomain.com
nameserver 999.999.99.9
nameserver 888.888.88.8
nameserver 777.777.77.7
search meineDomain.com meinedomain.net


Die sed-Befehle sollen dann in ein sh script, welches auf verschiedenen Servern ausgeführt wird. Die Anzahl der nameserver variert. Über die Zeile kann man also leider nicht gehen.

Könnt ihr mir helfen?
Danke schon mal im vorraus.

Lieben Gruß
OnePixel


----------



## Matthias Reitinger (24. Februar 2010)

Hallo OnePixel,

probier mal folgendes Shell-Skript:

```
#!/bin/sh

FILE="/etc/resolv.conf"
ENTRY="nameserver 666.666.66.6"

LINE=`sed -n '/^nameserver /=' "$FILE" | tail -n 1`
sed -i "${LINE}a${ENTRY}" "$FILE"
```

Grüße,
Matthias


----------



## OnePixel (24. Februar 2010)

ERSTMAL DANKE FÜR DIE SCHNELLE ANTWORT ;-)

habe es mal ausgeführt, jedoch mit folgender Fehlermeldung:

```
Syntax: tail [+/-[n][lbc][f]] [datei]
       tail [+/-[n][l][r|f]] [datei]
sed: Unzulässige Option -- i
```

Kann damit leider nichts anfangen....

habe selbst auch nochmal probiert:
mit folgendem Kommando bekomm ich eine Leerzeile unter allen Zeilen, die mit nameserver beginnen. Kennst du das Metazeichen, mit dem ich sage, 
dass er nur das letzte Vorkommen von nameserver benutzen soll?

```
cat /etc/resolv.conf | sed '/^nameserver/G'
```

Habe gehofft das man nur noch nen .? an die richtige Stelle fixieren müsste..... ;-)

Danke schonmal 
Lieben Gruß
OnePixel


----------



## Matthias Reitinger (24. Februar 2010)

OnePixel hat gesagt.:


> ERSTMAL DANKE FÜR DIE SCHNELLE ANTWORT ;-)
> 
> habe es mal ausgeführt, jedoch mit folgender Fehlermeldung:
> 
> ...


Oh, da hab ich wohl ein paar GNU-Erweiterungen verwendet. Probier es mal so:

```
#!/bin/sh

FILE="/etc/resolv.conf"
ENTRY="nameserver 666.666.66.6"
TMPFILE="`tempfile`"

LINE=`sed -n '/^nameserver /=' "$FILE" | tail -1`
sed "${LINE}a${ENTRY}" "$FILE" >"$TMPFILE"
mv "$TMPFILE" "$FILE"
```
Das -i bei sed sollte dafür sorgen, dass die Datei in-place bearbeitet wird. Kann man aber auch mit cat erreichen. \edit: Quatsch. Man muss eine temporäre Datei verwenden, wie deepthroat weiter unten anmerkt. Habe das Skript mal ausgebessert.



OnePixel hat gesagt.:


> habe selbst auch nochmal probiert:
> mit folgendem Kommando bekomm ich eine Leerzeile unter allen Zeilen, die mit nameserver beginnen. Kennst du das Metazeichen, mit dem ich sage,
> dass er nur das letzte Vorkommen von nameserver benutzen soll?


Ich bezweifle, dass das mit einem sed-Aufruf alleine funktioniert. sed arbeitet ja zeilenweise und man kann nicht feststellen, ob eine Zeile die letzte ihrer Art ist, ohne alle restlichen Zeilen durchzulaufen. Ich lasse mich da aber gerne eines besseren belehren, da ich kein Experte im Umgang mit sed bin.

Grüße,
Matthias


----------



## deepthroat (25. Februar 2010)

Hi.





Matthias Reitinger hat gesagt.:


> Ich bezweifle, dass das mit einem sed-Aufruf alleine funktioniert. sed arbeitet ja zeilenweise und man kann nicht feststellen, ob eine Zeile die letzte ihrer Art ist, ohne alle restlichen Zeilen durchzulaufen. Ich lasse mich da aber gerne eines besseren belehren, da ich kein Experte im Umgang mit sed bin.


Na, wenn das keine Herausforderung ist. 


```
#!/bin/sed -nf

H

/^nameserver /{
  s/^.*$//
  x
  s/^\n//
  p
}

${
  i\
nameserver blablabla
  g
  s/^\n//
  p
}
```
Gruß


----------



## OnePixel (25. Februar 2010)

Hallo nochmal ;-)

also habe das Problem jetzt gelöst. 
Meine Lösung:

RESOLV_CONF="/etc/resolv.conf"
AUSGABE_TAIL="`cat "${RESOLV_CONF}" | grep "nameserver" | tail -1`"

cat ${RESOLV_CONF} | sed "/${AUSGABE_TAIL}/G" | sed "s/^$/nameserver 333.33.333.3/" > ${RESOLV_CONF}

TADA 

EInen riesen dank an euch ! Thanks a lot 
Liebe Grüße 
OnePixel


----------



## OnePixel (25. Februar 2010)

Und ein riesen WOW an dich deepthroat.
Krasse Sache ;-)

DANKE


----------



## deepthroat (25. Februar 2010)

OnePixel hat gesagt.:


> RESOLV_CONF="/etc/resolv.conf"
> AUSGABE_TAIL="`cat "${RESOLV_CONF}" | grep "nameserver" | tail -1`"
> 
> cat ${RESOLV_CONF} | sed "/${AUSGABE_TAIL}/G" | sed "s/^$/nameserver 333.33.333.3/" > ${RESOLV_CONF}


Hast du das getestet? Normalerweise wird bei dieser Art Umleitung zuerstmal die Datei auf 0 Byte geschrumpft bevor cat überhaupt anfängt zu lesen. In der Regel ist das reine Glückssache falls es funktioniert.

Du mußt entweder die Datei erstmal komplett in den Speicher lesen (ist ja nicht so groß) oder temporäre Dateien verwenden.

Was für ein Betriebssystem verwendest du denn? Welche Shell?

Gruß

PS: Und bitte verwende die Code-Tags für deine Codeschnipsel!


----------



## Matthias Reitinger (25. Februar 2010)

deepthroat hat gesagt.:


> Hi.
> Na, wenn das keine Herausforderung ist.
> 
> […]


Du Teufelskerl, eine Reaktion von dir hatte ich schon befürchtet/erwartet ;-) Magst du vielleicht noch das Prinzip kurz erklären?

\edit: Der Backslash in der Zeile nameserver blablabla\ gehört aber weg, oder? Zumindest klappt es auf meinem System nur ohne.

Grüße,
Matthias


----------



## deepthroat (25. Februar 2010)

Matthias Reitinger hat gesagt.:


> Du Teufelskerl,


^^


Matthias Reitinger hat gesagt.:


> eine Reaktion von dir hatte ich schon befürchtet/erwartet ;-) Magst du vielleicht noch das Prinzip kurz erklären?


Ich dachte das wäre selbstdokumentierender Code?! 

Die grundlegende Vorgehensweise ist folgende:

Jede Zeile wird erstmal zum Hold-Space hinzugefügt (mit einem führenden Zeilenumbruch).

Wenn die Zeile mit _nameserver_ beginnt, dann wird der Pattern-Space gelöscht und mit dem Hold-Space vertauscht und es werden alle bisher gelesenen Zeilen ausgegeben (ohne den führenden Zeilenumbruch).

Das wiederholt sich bis die Verarbeitung am Ende der Eingabe angekommen ist (die $ Adresse). Bisher wurde immer bis zum zuletzt gelesenen nameserver Eintrag schon alles ausgegeben und es ist auch kein solcher Eintrag mehr aufgetreten, also muss jetzt der neue Eintrag ausgegeben werden und dann noch der restliche Text aus dem Hold-Space (ohne führenden Zeilenumbruch).

Man sollte das ganze evlt. doch lieber komplett in Bash schreiben. 


Matthias Reitinger hat gesagt.:


> \edit: Der Backslash in der Zeile nameserver blablabla\ gehört aber weg, oder? Zumindest klappt es auf meinem System nur ohne.


Richtig, der Backslash muss weg. Oder nach dem Backslash muss noch ein Leerzeichen folgen, welches der Highlighter hier unterschlagen hat. (irgendwie hat mich da die Formulierung der info Seiten irritiert). Außerdem fällt mir gerade auf, das der d Befehl überflüssig ist.

Gruß


----------



## OnePixel (26. Februar 2010)

Also das funktioniert super. Habe ein weiteres Script geschrieben, dass dieses Script auf Server kopiert, ausführt und anschließend wieder löscht.
Dieses Script wird in der Bourne-Shell (sh) ausgeführt. 
Das OS ist ein Solaris x86 Serversystem. Die Anzahl der Server sind ca. 60 und es hat alles super funktioniert. Mein Code den ich hereingestellt habe ist ja auch nur ein Teil des Scriptes.

Hier mal das komplette Script:

{noformat}

#!/bin/sh
#
###############
# Autor        : Sebastian Kachel
# Mail         : sebastian.kachel@xxxx.com
# Datum        : 24.02.2010
# Skriptname   : edit_resolv.conf
# Version      : 1.0
###############

# Abfrage ob Datei vorhanden ist und die Bearbeitung der resolv.conf
#
# Variablen:
#NS_SERVER="111.11.11.1"
NS2_SERVER="222.22.22.2"
NS3_SERVER="333.33.33.3"
NS3_SERVER_ALL="nameserver 333.33.33.3"
RESOLV_CONF="/etc/resolv.conf"
DATE="`date +"%Y%m%d"`"
IFNS3="`grep -c "333.33.33.3" /etc/resolv.conf`"
AUSGABE_TAIL="`cat "${RESOLV_CONF}" | grep "nameserver" | tail -1`"

if [ -f ${RESOLV_CONF} ] ; then
         cp ${RESOLV_CONF} ${RESOLV_CONF}.$DATE
         sed s/${NS2_SERVER}/${NS3_SERVER}/g ${RESOLV_CONF}.$DATE > ${RESOLV_CONF}
         cat ${RESOLV_CONF}
         echo "..................................................................."

        if [ "${IFNS3}" -gt "0" ] ; then
                echo "${RESOLV_CONF}  already done..............................."

        else
                cat /etc/resolv.conf | sed "/${AUSGABE_TAIL}/G" | sed "s/^$/nameserver 333.33.33.3/" > ${RESOLV_CONF}
                cat ${RESOLV_CONF}
                echo "${RESOLV_CONF}  already done..............................."
        fi



fi
{noformat}


Und das Script das Anhand einer Serverliste sich auf die Server über ssh einloggt und dann das Script (OBEN) kopiert, ausführt und wieder löscht.

{noformat}

#!/bin/ksh
#
###############
# Autor        : Sebastian Kachel
# Mail         : sebastian.kachel@xxx.com
# Datum        : 10.02.2010
# Skriptname   : verteiler.sh
# Version      : 1.0
###############
#
#Script, dass ein Script, auf der Auswahl einer Serverliste
#auf Server kopiert, ausführt und wieder löscht.
#
#Variablen:
SERVER=$SERVER" "`cat ~/Serverlists/SOLARIS`

[ $# -eq 1 ] && SERVER=$1

for server in $SERVER ; do
        ping "$server" 1 > /dev/null
        ping "$server" 1 > /dev/null
        ping "$server" 1 > /dev/null

        if [ "$?" = "0" ]; then
                echo "$server"
                scp /xxx/xx/xxxxxxx/edit_resolv_conf.sh root@${server}:/var/tmp/edit_resolv_conf.sh
                ssh root@${server} "sh /var/tmp/edit_resolv_conf.sh"
                ssh root@${server} "rm /var/tmp/edit_resolv_conf.sh"
        else
                echo "...$server is dead."
        fi
done


{noformat}


Aus Sicherheitsgründen sind IP und Phade durch xxx ersetzt ;-) 


Nochmls einen großen Dank an euch . Echt klasse ! 

Liebe Grüße 
OnePixel


----------



## deepthroat (26. Februar 2010)

OnePixel hat gesagt.:


> Also das funktioniert super.


Das scheint dann aber eine etwas unübliche Verhaltensweise der SunOS Bourne Shell zu sein.


			
				http://snap.nlc.dcccd.edu/learn/madden/intro/common/redirect.html hat gesagt.:
			
		

> Standard Output
> 
> There are two output redirection operators in UNIX.
> 
> ...


Und bitte fasse deine Codeschnipsel in [code]...[/code] Tags ein!

Gruß


----------

