• Willkommen im Linux Club - dem deutschsprachigen Supportforum für GNU/Linux. Registriere dich kostenlos, um alle Inhalte zu sehen und Fragen zu stellen.

[Final Version] Skript zum ändern in vielen Dateien

stka

Guru
Hallo an alle,

ich da mal ein kleines Skript geschrieben um Strings in vielen Textdateien mit Unterverzeichnissen zu ändern. Warum? Der Grund war ein Schreibfehler in einer Webseite. Dieser zog sich dank copy-and-past über mehrer Dateien in verschieden Unterverzeichnissen hin. Ein Änderung aller Dateien von Hand hätte etwas länger gedauert (ca. 100 Dateien). Aus diesem Grund ist das Skript entstanden.
Jetzt wird der eine oder andere sagen "das ist aber umständlich, das kann ich viel besser". Gerne dann zu verbessert es ;-). Diese Version ist in ca. 20 Minuten mit Test entstanden.
Code:
#!/bin/bash

clear
mkdir /tmp/aendern_tmp
echo
while [ -z "$QUELLE" ]
do
        echo -n "Bitte Quelle eingeben (QUIT für Ende): "
        read QUELLE
        if [ $QUELLE = "QUIT" ]
        then
                exit 1
        fi
        if [ ! -d $QUELLE ]
        then
                echo
                echo
                echo "$QUELLE ist kein Verzeichnis. Die Quelle muss ein Verzeichnis sein."
                echo
                exit 2
        fi
done
echo
while [ -z "$ALT" ]
do
        echo -n "Bitte alten Wert eingeben : "
        read ALT
done
echo
while [ -z "$NEU" ]
do
        echo -n "Bitte neuen Wert eingeben : "
        read NEU
done
for  i in `grep -lr  $ALT $QUELLE`
do
        echo "DATEI $i wird geändert"
        DATEI=`basename $i`
        PFAD=`dirname $i`
        if [ -f $i ]
        then
                sed  s/$ALT/$NEU/g $i > /tmp/aendern_tmp/$DATEI
                mv /tmp/aendern_tmp/$DATEI $PFAD/$DATEI
        fi
done
rmdir /tmp/aendern_tmp

Aber vielleicht kann es ja doch jemand gebrauchen ;-)

Gruß

Stefan
 

TeXpert

Guru
warum soll man daran groß was verbessern ;) vom Prinzip mache ich das auch so, 3 Kleinigkeiten sind mir aufgefallen:

1/

mkdir /tmp/aendern_tmp
dass kann schon existieren mit anderen Daten -> hier kann es zu Überschneidungen kommen schau Dir mal mktemp an, damit kannst Du eindeutige Dateinamen oder Verzeichnisnamen erzeugen.

2/

echo "DATEI $i wird geändert"
DATEI=`basename $i`
PFAD=`dirname $i`
if [ -f $i ]
then
sed s/$ALT/$NEU/g $i > /tmp/aendern_tmp/$DATEI
mv /tmp/aendern_tmp/$DATEI $PFAD/$DATEI
es kann nicht schaden, alles was mit Dateinamen in Bash-Scripten zu tun hat zu quoten.

3/

Aufräumen bei Problemen, schau Dir mal trap (man bash) an, und räum bei entsprechenden Signalen auf.
 

regexer

Advanced Hacker
stka schrieb:
sed s/$ALT/$NEU/g $i > /tmp/aendern_tmp/$DATEI
mv /tmp/aendern_tmp/$DATEI $PFAD/$DATEI
Ein paar Anmerkungen zu diesen Zeilen:
1. Den sed-Befehl würde ich in doppelte Hochkommas setzen. Was ist, wenn in $ALT oder $NEU ein space vorkommt?
2. Bitte beachten, dass es auch Metazeichen im sed gibt. Punkt und Stern sind einige davon. "la.er" findet z.B. auch "laber" und "laver".
3. Den move würde ich aus Sicherheitsgründen erst nach manuellem überprüfen der neuen Dateien durchführen. Die Gefahr, mir durch einen Tippfehler alle Dateien auf einmal zu versauen, wäre mir zu groß.
 
OP
S

stka

Guru
Jetzt sieht es so aus:
Code:
#!/bin/bash

clear
TMPDIR=`mktemp -d`
echo
while [ -z "$QUELLE" ]
do
        echo -n "Bitte Quelle eingeben (QUIT für Ende): "
        read QUELLE
        if [ $QUELLE = "QUIT" ]
        then
                exit 1
        fi
        if [ ! -d $QUELLE ]
        then
                echo
                echo
                echo "$QUELLE ist kein Verzeichnis. Die Quelle muss ein Verzeichnis sein."
                echo
                exit 2
        fi
done
while [ -z "$ZIEL" ]
do
        echo -n " Bitte Ziel eingeben : "
        read ZIEL
        if [ ! -d $ZIEL ]
        then
                if [ -f $ZIEL ]
                then
                        echo
                        echo " Das Ziel $ZIEL ist eine Datei, das Ziel muss ein Verzeichnis sein! "
                        exit 3
                fi
                mkdir "$ZIEL"
        fi
done
echo
while [ -z "$ALT" ]
do
        echo -n "Bitte alten Wert eingeben : "
        read ALT
done
echo
while [ -z "$NEU" ]
do
        echo -n "Bitte neuen Wert eingeben : "
        read NEU
done
for  i in `grep -lr  $ALT $QUELLE`
do
        echo "DATEI "$i" wird geändert"
        DATEI=`basename "$i"`
        PFAD=`dirname "$i"`
        if [ -f "$i" ]
        then
                sed  "s/$ALT/$NEU/g" $i > "$TMPDIR/$DATEI"
                mv "$TMPDIR/$DATEI" "$ZIEL/$DATEI"
        fi
done
rmdir "$TMPDIR"

@TexPert Das mit dem Aufräumen musst du mir noch mal erklären, wie du das meinst :shock: An welcher Stelle und was?

@notoxp Das Skript sollte eigentlich nur für mich sein. Aber die Hinweise baue ich gerne ein. Das mit dem "." sprich Regulären Ausdrücken kenne ich. Aber man kann ja nicht alles abfangen :wink: .

Das ist das gute hier, so kann man mit einiger Hilfe und ein paar zusätzlichen Ideen was vernünftiges hin bekommen. Auf weitere Anregungen und Änderungen bin ich gespannt. Wer will kann auch selber was hinzu fügen.

Gruß

Stefan
 

TeXpert

Guru
stka schrieb:
@TexPert Das mit dem Aufräumen musst du mir noch mal erklären, wie du das meinst :shock: An welcher Stelle und was?

man bash -> trap

Du sollst bei gängigen Signalen (z.B. Exit) das temp. Verzeichnis löschen.
 

regexer

Advanced Hacker
stka schrieb:
@TexPert Das mit dem Aufräumen musst du mir noch mal erklären, wie du das meinst :shock: An welcher Stelle und was?
TeXpert meint sicher, dass du Signale trappen solltest. Man kann alle Signale bis auf SIGKILL im Script abfangen.
Beispiel: Ganz am anfang eines Scripts steht folgender Befehl:
Code:
trap "echo Signal zwei" 2
Du startest das script, drückst aber gleich danach STRG+C und brichst es somit ab. Im Hintergrund wird in diesem Fall SIGINT (Signal Nr. 2) an den Prozess geschickt. Doch bevor der Prozess beendet wird, wird noch der echo ausgeführt. Statt des echos kannst du natürlich auch mit "rm -f" deine bereits erstellten temporären Dateien aufräumen.

Übrigens können auch noch andere Signale auftreten, je nach Situation.

Ich empfehle hierzu:
Code:
man 7 signal
 

TeXpert

Guru
genau, und zwar auf alle Signale, die das Skript terminieren, dann werden immer brav alle Temp.Files gleöscht
 
OP
S

stka

Guru
OK Ok :D, gebe mich geschlagen. Aber ihr habt recht, das macht Sinn. Ich werde das mal noch einarbeiten. Bin halt kein Programmierer sondern nun ein Netzwerker der ab und zu ein kleines Skript schreibt weil er zum Arbeiten zu faul ist ;-).
Aber ich bin ja lernfähig

Gruß

Stefan
 
OP
S

stka

Guru
Hallo TeXpert, hallo notoxp

so jetzt sieht es so aus:
Code:
#!/bin/bash

clear
TMPDIR=`mktemp -d`
echo
while [ -z "$QUELLE" ]
do
        echo -n "Bitte Quelle eingeben (QUIT für Ende): "
        trap "rmdir $TMPDIR; exit 11" 2
        read QUELLE
        if [ $QUELLE = "QUIT" ]
        then
                rmdir $TMPDIR
                exit 1
        fi
        if [ ! -d $QUELLE ]
        then
                echo
                echo
                echo "$QUELLE ist kein Verzeichnis. Die Quelle muss ein Verzeichnis sein."
                echo
                rmdir $TMPDIR
                exit 2
        fi
done
while [ -z "$ZIEL" ]
do
        echo -n " Bitte Ziel eingeben : "
        trap "rmdir $TMPDIR; exit 12" 2
        read ZIEL
        if [ ! -d $ZIEL ]
        then
                if [ -f $ZIEL ]
                then
                        echo
                        echo " Das Ziel $ZIEL ist eine Datei, das Ziel muss ein Verzeichnis sein!"
                        rmdir $TMPDIR
                        exit 3
                fi
                mkdir "$ZIEL"
        fi
done
echo
while [ -z "$ALT" ]
do
        echo -n "Bitte alten Wert eingeben : "
        trap "rmdir $TMPDIR; exit 13" 2
        read ALT
done
echo
while [ -z "$NEU" ]
do
        echo -n "Bitte neuen Wert eingeben : "
        trap "rmdir $TMPDIR; exit 14" 2
        read NEU
done
for  i in `grep -lr  $ALT $QUELLE`
do
        echo "DATEI "$i" wird geändert"
        DATEI=`basename "$i"`
        PFAD=`dirname "$i"`
        if [ -f "$i" ]
        then
                sed  "s/$ALT/$NEU/g" $i > "$TMPDIR/$DATEI"
                mv "$TMPDIR/$DATEI" "$ZIEL/$DATEI"
                trap "rm -rf $TMPDIR; exit 15" 2
        fi
done
rmdir "$TMPDIR"
Ich habe es getestet jetzt wird auf jeden Fall das temp Verzeichnis gelöscht wenn zu einem Zeitpunkt strg+c gedrückt wird. Bei allen Fehlern wird das temp Verzeichnis auch gelöscht.
Und wieder was dazu gelernt :p

Gruß

Stefan
 

TeXpert

Guru
die Trap setzt Du nur einmal am Anfang ;) 1. zeile oder so...

und siehe man bash:
If a sigspec is EXIT (0) the command arg is executed on exit from the shell.

pack auch noch ein EXIT als Signal rein, und als Aktion ein aufräumen, dann wird _imme_ das komplette Verzeichnis gelöscht, bei normalem Exit oder nicht.
 
OP
S

stka

Guru
OK. Jetzt mal nur der Anfang:
Code:
 #!/bin/bash
      2
      3 clear
      4 TMPDIR=`mktemp -d`
      5 trap "rmdir $TMPDIR; exit 9 "  0
      6 echo

Jetzt habe ich an allen Stellen im Skript das "rmdir $TMPDIR" entfernt. Das trap Kommando steht nur noch einmal ganz oben. Jetzt ist es egal ob ich das Skript vorzeitig mit strg+c oder mit QUIT abbreche oder das Skript bis zum Ende durchläuft, das TMP Verzeichnis wird immer gelöscht. Was ich jetzt noch nicht verstehe, aber vielleicht kann mir das einer von euche erklären, ist warum bei einem Abbruch mit strg+c das Verzeichnis auch gelöscht wird. Denn eigentlich müsste das doch ein SIGINT sein oder?

Gruß

Stefan
 

TeXpert

Guru
man 7 signal

1. SIGINT | 2 | A | Interrupt-Signal von der Tastatur

2. A Normalerweise wird der Prozess abgebrochen.

d.h. wenn das Signal2 nicht behandelt wird, dann wird der Default-handler aufgerufen und der ist ein quit, Beispiel:

Code:
#!/bin/bash

while read line
do
  :
done
Strg-C beendet die Schleife


Code:
#!/bin/bash
trap "echo 2" 2

while read line
do
  :
done
Strg-C gibt 2 aus und die Schleife läuft weiter.
 
OP
S

stka

Guru
Hallo TeXpert
Das verstehe ich jetzt. Da ich das Skript aber in der Schleife mit strg+c abbrechen können möchte, reicht doch oben ein trap mit dem Signal 0 und das TMP Verzeichnis wird auf jeden Fall gelöscht. Oder?

Gruß

Stefan
 

regexer

Advanced Hacker
stka schrieb:
Code:
      4 TMPDIR=`mktemp -d`
      5 trap "rmdir $TMPDIR; exit 9 "  0
1. Ein paar Anmerkungen: rmdir kann nur Verzeichnisse löschen, die keinen Inhalt haben.
2. wenn du trap "xxx" 0 machst, wird der Befehl bei einem normalen exit aus der Shell ausgeführt.
 
OP
S

stka

Guru
Final Version ;-)


Code:
#!/bin/bash

clear
# Erzeugt ein Temp-Verzeichnis in /tmp
TMPDIR=`mktemp -d`
# Loescht das Temp-Verzeichnis bei einem Programmabbruch und am Ende
trap "rmdir $TMPDIR; exit 9 "  0
echo
# Hier wird das Starverzeichnis festgelegt
while [ -z "$QUELLE" ]
do
        echo -n "Bitte Quelle eingeben (QUIT für Ende): "
        read QUELLE
# Wenn die Eingabe leer war, dann frage noch mal
        if [ -z "$QUELLE" ]
        then
                continue
        fi
# Wenn die Eingabe = QUIT Programmende
        if [ $QUELLE = "QUIT" ]
        then
                exit 1
        fi
# Hier wir ueberprueft ob die Quelle ein Verzeichnis ist
        if [ ! -d $QUELLE ]
        then
                echo
                echo
                echo "$QUELLE ist kein Verzeichnis. Die Quelle muss ein Verzeichnis sein."
                echo
                exit 2
        fi
done
# Jetzt wir das Zielverzeichnis eingelesen.
while [ -z "$ZIEL" ]
do
        echo -n " Bitte Ziel eingeben : "
        read ZIEL
        if [ ! -d $ZIEL ]
        then
# Wenn das Ziel eine Datei ist erfolgt ein Abbruch
                if [ -f $ZIEL ]
                then
                        echo
                        echo " Das Ziel $ZIEL ist eine Datei, das Ziel muss ein Verzeichnis sein!"
                        exit 3
                fi
# Wenn das Ziel nicht existiert wird es angelegt
                mkdir "$ZIEL"
        fi
done
echo
# Hier wird der zu ersetzende Wert eingelesen ACHTUNG bei
# Leerzeichen "text immer" quoten
while [ -z "$ALT" ]
do
        echo -n "Bitte alten Wert eingeben : "
        read ALT
done
echo
# Das gleiche für den neuen Wert.
while [ -z "$NEU" ]
do
        echo -n "Bitte neuen Wert eingeben : "
        read NEU
done
# In einer Schleife werden rekursiev alle Dateien
# nach dem alten Wert durchsucht und geändert
for  i in `grep -lr  $ALT $QUELLE`
do
        echo "DATEI "$i" wird geändert"
# Hier wir der Pfad zur aktuellen Datei ermittelt
        DATEI=`basename "$i"`
# Hier wir der Dateiname der aktuellen Datei ermittelt
        PFAD=`dirname "$i"`
        if [ -f "$i" ]
        then
# Hier wird nun wirklich ersetzt
                sed  "s/$ALT/$NEU/g" $i > "$TMPDIR/$DATEI"
# Und an neue Ziel verschoben
                mv "$TMPDIR/$DATEI" "$ZIEL/$DATEI"
        fi
done
 

harley

Hacker
Tolles Skript. Zwei Sachen fehlen mir: 1) die Ordnerstruktur wird nicht übernommen 2) Links können nicht geändert werden, da das Slash-Zeichen als Trenner für sed verwendet wird.

Code:
# Hier wird nun wirklich ersetzt 
              sed  "s|$ALT|$NEU|g" $i > "$TMPDIR/$DATEI" 
 
# An dieser Stelle wird der Verzeichnisbaum erstellt
				  PFADNEU=${PFAD#$QUELLE}
				  if [ ! -d $ZIEL/$PFADNEU ]
				  then			  	
				  			mkdir -p "$ZIEL/$PFADNEU"
				  fi
					              
# Datei wird ans neue Ziel verschoben 
              mv "$TMPDIR/$DATEI" "$ZIEL/$PFADNEU/$DATEI"

Micha
 

Yehudi

Guru
Ich würde das Script gerne ins Wiki übernehmen, aber der Titel klingt etwas zu oberflächlich. Der Titel sollte möglichst kurz und prägnant sein.
 
Oben