Bagaimana saya bisa melakukan pencarian dan penggantian rekursif dari baris perintah?

84

Menggunakan shell seperti bash atau zshell, bagaimana saya bisa melakukan 'temukan dan ganti' secara rekursif? Dengan kata lain, saya ingin mengganti setiap kemunculan 'foo' dengan 'bar' di semua file dalam direktori ini dan subdirektori.

Nathan Long
sumber
Jawaban alternatif untuk pertanyaan yang sama dapat ditemukan di sini stackoverflow.com/questions/9704020/…
dunxd
Mungkin ide yang baik untuk mencoba ini di vim. Dengan begitu Anda dapat menggunakan fitur konfirmasi untuk memastikan Anda tidak menukar sesuatu yang tidak Anda inginkan. Saya tidak yakin apakah itu bisa dilakukan direktori lebar.
Samy Bencherif

Jawaban:

106

Perintah ini akan melakukannya (diuji pada Mac OS X Lion dan Kubuntu Linux).

# Recursively find and replace in files
find . -type f -name "*.txt" -print0 | xargs -0 sed -i '' -e 's/foo/bar/g'

Begini cara kerjanya:

  1. find . -type f -name '*.txt'menemukan, dalam direktori saat ini ( .) dan di bawah, semua file biasa ( -type f) yang namanya berakhir dengan.txt
  2. | meneruskan output dari perintah itu (daftar nama file) ke perintah berikutnya
  3. xargs mengumpulkan nama file itu dan menyerahkannya satu per satu ke sed
  4. sed -i '' -e 's/foo/bar/g'berarti "mengedit file di tempat, tanpa cadangan, dan melakukan penggantian berikut ( s/foo/bar) beberapa kali per baris ( /g)" (lihat man sed)

Perhatikan bahwa bagian 'tanpa cadangan' di baris 4 OK untuk saya, karena file yang saya ubah berada di bawah kontrol versi, jadi saya dapat dengan mudah membatalkan jika ada kesalahan.

Nathan Long
sumber
9
Jangan pernah pipa mencari output ke xargs tanpa -print0opsi. Perintah Anda akan gagal pada file dengan spasi dll dalam namanya.
slhck
20
Juga, find -name '*.txt' -exec sed -i 's/foo/bar/g' {} +lakukan semua ini dengan GNU find.
Daniel Andersson
6
Saya dapatkan sed: can't read : No such file or directoryketika saya menjalankan find . -name '*.md' -print0 | xargs -0 sed -i '' -e 's/ä/ä/g', tetapi find . -name '*.md' -print0memberikan daftar banyak file.
Martin Thoma
9
Ini berfungsi untuk saya jika saya menghapus ruang antara -idan''
Luke Kanada. Reinstate MONICA
3
Apa artinya ''setelah sed -iapa ''peran?
Jas
27
find . -type f -name "*.txt" -exec sed -i'' -e 's/foo/bar/g' {} +

Ini menghilangkan xargsketergantungan.

Ztyx
sumber
4
Ini tidak berfungsi dengan GNU sed, jadi akan gagal pada kebanyakan sistem. GNU sedmengharuskan Anda untuk tidak memberi jarak antara -idan ''.
slhck
1
Jawaban yang diterima lebih baik menjelaskannya. tetapi +1 untuk menggunakan sintaks yang benar.
orodbhen
9

Jika Anda menggunakan Git maka Anda dapat melakukan ini:

git grep -lz foo | xargs -0 sed -i '' -e 's/foo/bar/g'

-lhanya mencantumkan nama file. -zmencetak byte nol setelah setiap hasil.

Saya akhirnya melakukan ini karena beberapa file dalam proyek tidak memiliki baris baru di akhir file, dan menambahkan baris baru bahkan ketika itu tidak membuat perubahan lain. (Tidak ada komentar apakah file harus memiliki baris baru di akhir. 🙂)

David Winiecki
sumber
1
Besar +1 untuk solusi ini. Sisa dari find ... -print0 | xargs -0 sed ...solusi tidak hanya membutuhkan waktu lebih lama tetapi juga menambahkan baris baru ke file yang belum memilikinya, yang merupakan gangguan ketika bekerja di dalam git repo. git greppencahayaan cepat sebagai perbandingan.
Josh Kupershmidt
3

Inilah fungsi zsh / perl saya yang saya gunakan untuk ini:

change () {
        from=$1 
        shift
        to=$1 
        shift
        for file in $*
        do
                perl -i.bak -p -e "s{$from}{$to}g;" $file
                echo "Changing $from to $to in $file"
        done
}

Dan saya akan mengeksekusinya menggunakan

$ change foo bar **/*.java

(sebagai contoh)

Brian Agnew
sumber
2

Mencoba:

sed -i 's/foo/bar/g' $(find . -type f)

Diuji pada Ubuntu 12,04.

Perintah ini TIDAK akan berfungsi jika nama subdirektori dan / atau nama file mengandung spasi, tetapi jika Anda memilikinya, jangan gunakan perintah ini karena tidak akan berfungsi.

Ini adalah praktik yang buruk untuk menggunakan spasi dalam nama direktori dan nama file.

http://linuxcommand.org/lc3_lts0020.php

Lihat "Fakta penting tentang nama file"

nexayq
sumber
Cobalah ketika Anda memiliki file dengan spasi di namanya. (Ada aturan praktis yang mengatakan, "Jika sesuatu tampak terlalu bagus untuk menjadi kenyataan, itu mungkin benar." Jika Anda telah "menemukan" solusi yang lebih kompak daripada apa pun yang diposkan orang lain dalam 3½ tahun, Anda harus bertanya pada diri sendiri mengapa itu mungkin.)
Scott
1

Gunakan Script Shell ini

Saya sekarang menggunakan skrip shell ini, yang menggabungkan hal-hal yang saya pelajari dari jawaban lain dan dari pencarian di web. Saya menempatkannya di file yang disebut changefolder $PATHdan saya lakukan chmod +x change.

#!/bin/bash
function err_echo {
  >&2 echo "$1"
}

function usage {
  err_echo "usage:"
  err_echo '  change old new foo.txt'
  err_echo '  change old new foo.txt *.html'
  err_echo '  change old new **\*.txt'
  exit 1
}

[ $# -eq 0 ] && err_echo "No args given" && usage

old_val=$1
shift
new_val=$1
shift
files=$* # the rest of the arguments

[ -z "$old_val" ]  && err_echo "No old value given" && usage
[ -z "$new_val" ]  && err_echo "No new value given" && usage
[ -z "$files" ]    && err_echo "No filenames given" && usage

for file in $files; do
  sed -i '' -e "s/$old_val/$new_val/g" $file
done
Nathan Long
sumber
0

Kasus penggunaan saya adalah saya ingin mengganti foo:/Drive_Letterdengan foo:/bar/baz/xyz Dalam kasus saya, saya bisa melakukannya dengan kode berikut. Saya berada di lokasi direktori yang sama di mana ada banyak file.

find . -name "*.library" -print0 | xargs -0 sed -i '' -e 's/foo:\/Drive_Letter:/foo:\/bar\/baz\/xyz/g'

harapan itu membantu.

neo7
sumber
0

Perintah berikut bekerja dengan baik di Ubuntu dan CentOS; Namun, di bawah OS XI terus mendapatkan kesalahan:

find . -name Root -exec sed -i 's/1.2.3.4\/home/foo.com\/mnt/' {} \;

sed: 1: "./Root": kode perintah tidak valid.

Ketika saya mencoba melewati params melalui xargs itu berfungsi dengan baik tanpa kesalahan:

find . -name Root -print0 | xargs -0 sed -i '' -e 's/1.2.3.4\/home/foo.com\/mnt/'
John Morgan
sumber
Fakta bahwa Anda berubah -imenjadi -i ''mungkin lebih relevan daripada fakta bahwa Anda berubah -execmenjadi -print0 | xargs -0. BTW, Anda mungkin tidak perlu -e.
Scott
0
# Recursively find and replace in files
find . -type f -name "*.txt" -print0 | xargs -0 sed -i '' -e 's/foo/bar/g'

Di atas berfungsi seperti pesona, tetapi dengan direktori yang ditautkan, saya harus menambahkan -Lbendera padanya. Versi finalnya terlihat seperti:

# Recursively find and replace in files
find -L . -type f -name "*.txt" -print0 | xargs -0 sed -i '' -e 's/foo/bar/g'
sMajeed
sumber