Einen String rekursiv ersetzen

Vom einfachen Programm zum fertigen Debian-Paket, Fragen rund um Programmiersprachen, Scripting und Lizenzierung.
Antworten
Benutzeravatar
za0
Beiträge: 816
Registriert: 09.07.2005 00:14:18
Wohnort: das 4. Reich der GEZ

Einen String rekursiv ersetzen

Beitrag von za0 » 01.07.2012 18:58:47

Hallo liebe Debiangemeinde,

ich möchte möglichst mit wenig Aufwand case-sensitiv eine Zeichenkette ersetzen. Dies soll auch Verzeichnisse und Dateien betreffen, d.h.

wir haben beispielsweise Dateien a.cpp, b.cpp und im Ordner foo die Datei foo.cpp.
Der String "foo" befindet sich in allen 3 Dateien, also in a.cpp, b.cpp und foo.cpp.
Außerdem heißt das Verzeichnis foo und die Datei cpp-Datei in diesem Ordner ebenso.

Wenn "foo" jett substituiert werden soll,, sagen wir durch "xxl", dann soll es nicht nur in den Dateien passieren, sondern Ordner und Dateien, die "foo" beinhalten, sollen umbenannt werden.
Nach einer xxl-Substitution hätte man dann a.cpp und b.cpp mit xxl statt foo drin, den ordner xxl mit darin enthaltener xxl.cpp-datei mit allen von foo zu xxl umgewandelten einträgen.

ich habe mir schonmal überlegt, wie es losgehen soll:

Code: Alles auswählen

find . -name "*" -exec ./go.sh {} <substring> \;
go.sh sieht dann so aus

Code: Alles auswählen

#!/bin/bash
echo $1
echo $2

#dateinamen anpassen
mv $1* $2*

# todo
 # in den dateien substituieren
exit 0;
Ist jetzt noch die Frage offen, ob es nicht zu probs kommt, wenn mehrere dateien umbenannt werden müssen und sich imselben verzeichnis befinden.

Viele Grüße
za0


Nieder mit der Pauschal-Abzocke der GEZ! :twisted:

Cae
Beiträge: 6349
Registriert: 17.07.2011 23:36:39
Wohnort: 2130706433

Re: Einen String rekursiv ersetzen

Beitrag von Cae » 01.07.2012 19:46:51

Damit ich dich richtig verstehe, du hast einen String "foo", der sowohl in
  1. Verzeichnissnamen,
  2. Dateinamen und
  3. in den Dateien selbst
vorkommen kann. Er soll in allen drei Fällen durch "bar" ersetzt werden. Richtig?

Problem eins und zwei kann man zusammenfassen:

Code: Alles auswählen

find . -name '*foo*' -exec sh -c 'mv "{}" "$(echo "{}" | sed s/foo/bar/g)"' \;
oder alternativ mit -type f oder -type d auf Dateien oder Verzeichnisse eingrenzen.
Das Problem dabei ist, dass bei einem Pfad foo/test/foo zuerst zu bar/test/foo ersetzt wird und anschließend versucht wird, foo/test zu betreten, was es aber nicht mehr gibt. Also muss man das hinterher umbenennen, eher so etwas:

Code: Alles auswählen

$(find . -name '*foo*' | awk '{sh = sh "mv " $0 " "; gsub("foo", "bar"); sh = sh $0 "\n"} END {print sh}')
Da hat man aber das gleiche Problem, nur anders. find geht vom Stamm aus über jeden Zweig zu einem Blatt, für das Umbenennen müsst man aber erst alle infrage kommenden Blätter, dann die äußeren Zweige, die Äste und schließlich den Stamm bearbeiten. Ich frage mich, wie man das macht (evtl. kann find das und es steht in der Manpage?).

Für den dritten Punkt sollte

Code: Alles auswählen

find . -type f -exec sed -i 's/foo/bar/g' \;
unproblematisch sein.

Gruß Cae
If universal surveillance were the answer, lots of us would have moved to the former East Germany. If surveillance cameras were the answer, camera-happy London, with something like 500,000 of them at a cost of $700 million, would be the safest city on the planet.

—Bruce Schneier

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

Re: Einen String rekursiv ersetzen

Beitrag von Meillo » 01.07.2012 20:05:47

Das Programm emv [0] koennte auch hilfreich sein. Allerdings musst du dazu Dateien und Verzeichnisse gesondert behandeln.

[0] http://www.i0i0.de/toolchest/emv


Ansonsten, als Ansatzpunkt biete ich Folgendes. Achtung: Dieser Code ist ungetestet!

Code: Alles auswählen

for i in `find . -name '*foo*' | tac` ; do
	new="`echo $i | sed 's/foo\([^/]*\)/bar\1/g'`"  # replace last foo only
	# new="`dirname $i`/`basename $i | sed 's/foo/bar/g'`"  # alternative
	if [ -e "$new" ] ; then
		echo "cannot rename $i because $new already exists" >&2
		continue
	fi
	mv "$i" "$new"
done

Was das Ersetzen von Text angeht, so hat Cae die Loesung schon geliefert.
Use ed once in a while!

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

Re: Einen String rekursiv ersetzen

Beitrag von Meillo » 02.07.2012 18:11:02

Cae hat geschrieben: Das Problem dabei ist, dass bei einem Pfad foo/test/foo zuerst zu bar/test/foo ersetzt wird und anschließend versucht wird, foo/test zu betreten, was es aber nicht mehr gibt.
Hierzu habe ich gerade `-depth' in der Manpage find(1) gesehen:
-depth
Process each directory's contents before the directory itself.
Das scheint genau dieses Problem zu loesen. Kann das jemand bestaetigen?
Use ed once in a while!

Cae
Beiträge: 6349
Registriert: 17.07.2011 23:36:39
Wohnort: 2130706433

Re: Einen String rekursiv ersetzen

Beitrag von Cae » 02.07.2012 21:18:16

Code: Alles auswählen

~/tmp/find % mkdir -p {0..2}/{0..2}/{0..2}
~/tmp/find % find . -type d -exec sh -c 'touch "{}/normal-$(date +%s%N)"; sleep 0.1' \;
~/tmp/find % find . -depth -type d -exec sh -c 'touch "{}/depth-$(date +%s.%N)"; sleep 0.1' \;
~/tmp/find % find . -type f -name '*normal*' | sort -t- -k2 | head; echo; \
             find . -type f -name '*depth*'  | sort -t- -k2 | head
./normal-1341256168447902907
./1/normal-1341256168556580307
./1/1/normal-1341256168664976985
./1/1/1/normal-1341256168773249750
./1/1/0/normal-1341256168881459666
./1/1/2/normal-1341256168989525636
./1/0/normal-1341256169097657737
./1/0/1/normal-1341256169205784446
./1/0/0/normal-1341256169313875071
./1/0/2/normal-1341256169421919859

./1/1/1/depth-1341256175.863986950
./1/1/0/depth-1341256175.972347316
./1/1/2/depth-1341256176.080568253
./1/1/depth-1341256176.188819709
./1/0/1/depth-1341256176.296992581
./1/0/0/depth-1341256176.405036916
./1/0/2/depth-1341256176.513170212
./1/0/depth-1341256176.621209255
./1/2/1/depth-1341256176.730169173
./1/2/0/depth-1341256176.838335647
Das scheint genau das gesuchte Flag zu sein. Die normale Variante steigt an der "Vorderkante" des Baumes entlang, -depth sorgt für die Flutung vom äußersten Zweig an. Wobei jetzt mal offen bleibt, warum find unbedingt die Reihenfolge 1-0-2 wählt… da wird wahrscheinlich einfach die rohe Reihenfolge vom Syscall übernommen.

Gruß Cae
If universal surveillance were the answer, lots of us would have moved to the former East Germany. If surveillance cameras were the answer, camera-happy London, with something like 500,000 of them at a cost of $700 million, would be the safest city on the planet.

—Bruce Schneier

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

Re: Einen String rekursiv ersetzen

Beitrag von Meillo » 02.07.2012 22:14:29

Cae hat geschrieben: Wobei jetzt mal offen bleibt, warum find unbedingt die Reihenfolge 1-0-2 wählt… da wird wahrscheinlich einfach die rohe Reihenfolge vom Syscall übernommen.
Ja. Verzeichniseintraege werden von readdir() in irgendeiner Ordnung zurueck gegeben. Die zu sortieren ist Mehraufwand und man muss warten bis der letzte Eintrag gelesen wurde bevor man irgendwas ausgeben kann. Find muesste zudem den kompletten Verzeichnisbaum in den Speicher laden, da der letzte Eintrag ja der als erstes auszugebende sein koennte. Gibt man die Eintraege in der gelieferten Reihenfolge on-the-fly aus, dann muss man nur den aktuellen Eintrag im Speicher halten, plus die Rekursion an sich, natuerlich.

Dass hier die Reihenfolge immer 1-0-2 ist, sollte vom verwendeten Dateisystem abhaengen. Bei meinem ext3 kommen die Eintraege einfach in umgedrehter Reihenfolge zurueck -- neuste Datei zuerst. (Statt mit find kann man das auch mit `ls -f' rausfinden.)
Use ed once in a while!

Antworten