Grep-Rohrleitungen in Sed, die Inline ersetzen; aber ich möchte, dass sed den Dateinamen und die geänderte Zeile druckt. Ist es möglich?

7

Hier ist mein Befehl (absichtlich brechen):

grep FOO "/Users/gjtorikian/blah" -l | xargs sed -i '' '/FOO/{s/FOO/BAR/g; w /dev/stdout
}'

Auf hoher Ebene: grepfür FOO im blahVerzeichnis; Pipe in nur den Dateinamen (wegen -l) zu sed; sedführt ein Inline-Ersetzen ( -i '') durch und druckt nur den geänderten Begriff an /dev/stdout.

Wenn ich das -lund die Pipe weglassen würde , bekomme ich dies zurück von grep:

/Users/gjtorikian/blah/baz.cs:1:FOO
/Users/gjtorikian/blah/bar.js:1:FOO

Ich möchte seddas Inline-Ersetzen durchführen und mir dann die ersetzte Datei und den ersetzten Begriff anzeigen. zum Beispiel:

/Users/gjtorikian/blah/baz.cs:1:BAR
/Users/gjtorikian/blah/bar.js:1:BAR

Ist so etwas möglich? Wenn es darauf ankommt, würde ich es vorziehen, es nur mit grep/ zu behalten sed. Muss ich eine Sekunde grepnach dem machen sed?

gjtorikian
quelle
Ich sehe keine Möglichkeit, den aktuellen Dateinamen in sed abzurufen, daher benötigen Sie wahrscheinlich einen zweiten Grep.
Kevin
Außerdem brauchen Sie das nicht '', das ist die Standardeinstellung.
Kevin
3
Unter OS X ''ist dies nicht die Standardeinstellung.
Gjtorikian

Antworten:

4

Eine Möglichkeit wäre, die Ausgabe grepan einen separaten sedFilter weiterzuleiten .

grep -l FOO "/Users/gjtorikian/blah/"* |
{ tee /dev/fd/3 |
  xargs sed -i -e '/FOO/{' -e 's/FOO/BAR/g' -e 'w /dev/stdout'
} 3>&1 | sed 's/:FOO$/:BAR/'

Sie können seddie Zeilennummer (mit dem =Befehl) drucken lassen, wenn eine Übereinstimmung gefunden wird, und eine weitere Nachbearbeitung durchführen.

Es wäre wahrscheinlich klarer, awk zu verwenden.

for x in "/Users/gjtorikian/blah/"*; do
  awk '
    sub(/FOO/, "BAR") {found=1; print FILENAME ":" NR ":" "BAR" >"/dev/stderr"}
    1 {print}
    END {exit(!found)}
' "$x" >"$x.tmp" && mv "$x.tmp" "$x"
done
Gilles 'SO - hör auf böse zu sein'
quelle
2

Da ich kein englischer Muttersprachler bin, habe ich es wahrscheinlich nicht verstanden.

Um ein Verzeichnis zu "grep", benötigen Sie "-r". Die Verwendung von '-l' druckt nur den Dateinamen und hört nach dem ersten Auftreten auf zu greifen.

# pattern=/home ; grep -l "$pattern" /etc/[a-z]* 2>/dev/null | while read line ; do echo "$line:$pattern" ; done
/etc/adduser.conf:/home
/etc/fstab:/home
/etc/fstab~:/home
/etc/libuser.conf:/home
/etc/mpd.conf:/home
/etc/mpd.conf~:/home
/etc/mtab:/home
/etc/netscsid.conf:/home
/etc/passwd:/home
/etc/passwd-:/home
/etc/sudoers:/home
/etc/sudoers.tmp~:/home
Jirib
quelle
1

Ich denke, Sie machen das Problem zu kompliziert, indem Sie versuchen, es auf einmal zu tun. Sie können einfach eine tun

grep -H -n /Users/gjtorikian/blah | sed 's/\(^.*?:[0-9]+?:\).*FOO.*/\1BAZ/'

um die Liste der Dateien mit Zeilennummern und Ersetzungen zu erhalten (dies sollte funktionieren, solange Ihre Dateinamen keine Doppelpunkte enthalten, aber das ist unter Mac OS trotzdem eine schlechte Idee ...). Anschließend können Sie eine ausstellen

sed -i '' 's/FOO/BAR/g' /Users/gjtorikian/blah

Hier wird kein grep und xarg benötigt (Sie können ein "find ... | xarg" durchführen, wenn Sie jedoch viele Dateien haben). Wenn Sie über die Duplikate besorgt sind, können Sie die beiden Zeilen in ein Skript oder eine Funktion einfügen und dort Variablen ersetzen.

David Ongaro
quelle
sed -i ...Ändert die Dateizeiten, auch wenn der Stream unverändert bleibt.
@jww: Sie haben Recht, das liegt daran, dass sedein Stream-Editor kein Datei-Editor ist, sodass die -iOption gegen die Unix-Philosophie verstößt ( mywiki.wooledge.org/BashFAQ/021#Using_nonstandard_tools ). Wenn Sie diese Verwendung vermeiden möchten ex(z ex -sc '%s/FOO/BAR/ge|x' /Users/gjtorikian/blah. B. ).
David Ongaro
1

Ersetzen Sie FOO rekursiv durch BAR im aktuellen Verzeichnis (leiser Modus).

grep -rl 'FOO' | xargs sed -i 's/FOO/BAR/'
Terentev Maksim
quelle
0

Einzeiler

grep -n FOO * -R|awk -F\: '{ print $1 }'|sort|uniq|while read file; do sed -i 's/FOO/BAR/g' $file; done
Neal Garrett
quelle