Cari dan ganti di Vim di semua file proyek

117

Saya mencari cara terbaik untuk melakukan pencarian dan penggantian (dengan konfirmasi) di semua file proyek di Vim. Yang saya maksud dengan "file proyek" adalah file di direktori saat ini, beberapa di antaranya tidak harus dibuka.

Salah satu cara untuk melakukannya adalah dengan membuka semua file di direktori saat ini:

:args ./**

lalu lakukan pencarian dan ganti pada semua file yang terbuka:

:argdo %s/Search/Replace/gce

Namun, ketika saya melakukan ini, penggunaan memori Vim melonjak dari beberapa lusin MB menjadi lebih dari 2 GB, yang tidak berfungsi untuk saya.

Saya juga menginstal plugin EasyGrep , tetapi hampir tidak pernah berfungsi — baik plugin tidak menemukan semua kemunculannya, atau hanya hang sampai saya menekan CtrlC. Sejauh ini cara yang saya sukai untuk menyelesaikan tugas ini adalah ack-grep untuk istilah pencarian, dengan menggunakan jendela perbaikan cepat buka file apa pun yang berisi istilah dan tidak dibuka sebelumnya, dan akhirnya :bufdo %s/Search/Replace/gce.

Saya sedang mencari plugin yang bagus dan berfungsi yang dapat digunakan untuk ini, atau alternatif perintah / urutan perintah yang akan lebih mudah daripada yang saya gunakan sekarang.

psyho
sumber
1
@Cascabel Karena Anda menulis komentar ini, ada sebuah situs vi.stackexchange.com.
Flimm

Jawaban:

27

Greplace bekerja dengan baik untukku.

Ada juga versi siap patogen di github.

tuxcanfly.dll
sumber
8
Menginstalnya, saya melihat perintah lain suka :Gsearchdan :Gbuffersearchada, tetapi ketika saya mengetik :Greplacesaya mendapatkan Not an editor command: Greplace.
Ramon Tayag
menurut dokumen, sintaksnya adalah: :Gsearch [<grep-option(s)>] [[<pattern>] [<filename(s)>]] jadi Anda bisa melakukan pencarian rekursif menggunakan, misalnya::Gsearch -r pattern dir/
vitorbal
Hal yang saya benci tentang Greplace adalah jika polanya ada di nama file, Greplace gagal, karena Anda akhirnya menggantinya di nama file juga.
Daniel
2
Tidak ada yang tidak biasa di sini setelah 6 tahun, tetapi plugin ini sangat usang dibandingkan dengan beberapa jawaban baru ( github.com/yegappan/greplace ) pembaruan terakhir, 3+ tahun yang lalu. Saya mungkin memeriksa solusi bawaan oleh Sid: stackoverflow.com/a/38004355/2224331 (Bukan saya di akun lain, hanya kebetulan: P)
SidOfc
99

Opsi besar lainnya di sini adalah tidak menggunakan vim:

sed -i 's/pattern/replacement/' <files>

atau jika Anda memiliki beberapa cara untuk membuat daftar file, mungkin sesuatu seperti ini:

find . -name *.cpp | xargs sed -i 's/pattern/replacement/'
grep -rl 'pattern1' | xargs sed -i 's/pattern2/replacement/'

dan seterusnya!

Bertingkat
sumber
Terima kasih! Saya benar-benar perlu mempelajari hal-hal baris perintah sekolah tua ini. Saya rasa ini adalah jawaban terbaik. Apakah ini regexes perl regexes atau vim regexes atau sejenis regex lainnya?
Eric Johnson
2
@EricJohnson: Mereka ... sedregex, yang pada dasarnya adalah ekspresi reguler dasar POSIX.2, tetapi tidak persis (ini ada di halaman manual) - mereka membiarkan \nbaris baru cocok, dan beberapa hal serupa lainnya. Oleh karena itu, mereka hampir sama dengan grepekspresi reguler.
Cascabel
Apakah ada alasan Anda memiliki -i dalam perintah sed? Halaman manual mengatakan itu untuk menentukan ekstensi, tetapi Anda tampaknya tidak benar-benar menentukannya.
davekaro
Ok sebenarnya sepertinya 's / pattern / replacement /' adalah ekstensinya, saya rasa saya mengerti sekarang.
davekaro
11
Di Mac OS X, satu-satunya perintah sed yang berhasil untuk saya adalah:find . -type f | LANG=C xargs sed -i '' 's/SEARCH/REPLACE/g'
ghodss
74

EDIT: Gunakan cfdoperintah alih-alih cdountuk secara signifikan mengurangi jumlah perintah yang akan dijalankan untuk mencapai ini (karena cdomenjalankan perintah pada setiap elemen saat cfdomenjalankan perintah pada setiap file)

Berkat cdoperintah yang baru saja ditambahkan , Anda sekarang dapat melakukan ini dalam dua perintah sederhana menggunakan grepalat apa pun yang telah Anda instal. Tidak diperlukan plugin tambahan !:

1. :grep <search term>
2. :cdo %s/<search term>/<replace term>/gc
3. (If you want to save the changes in all files) :cdo update

( cdomenjalankan perintah yang diberikan ke setiap istilah dalam daftar quickfix, yang grepdiisi oleh perintah Anda .)

(Hapus cdi akhir perintah ke-2 jika Anda ingin mengganti setiap istilah pencarian tanpa mengonfirmasi setiap kali)

Sid
sumber
12
Ditambah Anda dapat menambahkan :cdo updateuntuk menyimpan perubahan di semua file.
Zhe Li
1
Tapi itu akan berhenti sejenak untuk memperingatkan tidak disimpan setiap kali sebelum beralih ke buffer berikutnya ketika buffer saat ini diubah.
Bebas
1
@Zhe terima kasih, saya telah menambahkan itu ke jawaban; @lfree Saya pribadi lebih suka mengonfirmasi setiap perubahan, tetapi Anda dapat menghapus cdi akhir perintah kedua (cukup gunakan g) untuk membuatnya mencari dan mengganti tanpa meminta konfirmasi
Sid
1
: cdo% s / istilah pencarian / ganti istilah / g hanya menggantikan kemunculan pertama, tetapi tidak berfungsi untuk kemunculan kedua di vim saya
sunxd
3
untuk menggunakan update, saya harus:cdo %s/<search term>/<replace term>/g | update
gabe
34

Saya telah memutuskan untuk menggunakan ack dan Perl untuk memecahkan masalah ini untuk memanfaatkan ekspresi reguler Perl yang lebih kuat daripada subset GNU.

ack -l 'pattern' | xargs perl -pi -E 's/pattern/replacement/g'

Penjelasan

ack

Jack adalah alat baris perintah mengagumkan yang merupakan campuran grep, finddan penuh Perl ekspresi reguler (bukan hanya GNU bagian). Ini ditulis dalam Perl murni, cepat, memiliki penyorotan sintaks, bekerja pada Windows dan lebih ramah bagi pemrogram daripada alat baris perintah tradisional. Instal di Ubuntu dengan sudo apt-get install ack-grep.

xargs

Xargs adalah alat baris perintah unix lama. Itu membaca item dari input standar dan menjalankan perintah yang ditentukan diikuti dengan item yang dibaca untuk input standar. Jadi pada dasarnya daftar file yang dihasilkan oleh ack akan ditambahkan ke akhir perl -pi -E 's/pattern/replacemnt/g'perintah.

perl -pi

Perl adalah bahasa pemrograman. Opsi -p menyebabkan Perl membuat loop di sekitar program Anda yang mengiterasi argumen nama file. Opsi -i menyebabkan Perl mengedit file di tempatnya. Anda dapat memodifikasi ini untuk membuat cadangan. Opsi -E menyebabkan Perl untuk mengeksekusi satu baris kode yang ditentukan sebagai program. Dalam kasus kami, program ini hanyalah substitusi regex Perl. Untuk informasi lebih lanjut tentang opsi baris perintah Perl perldoc perlrun. Untuk informasi lebih lanjut tentang Perl lihat http://www.perl.org/ .

Eric Johnson
sumber
Saya suka jawaban ini karena berfungsi bahkan dengan akhir baris cygwin. Perhatikan bahwa itu menambahkan \ r ke akhir baris yang dimodifikasi, tetapi ketika menggunakan sed, itu akan menggantikan setiap baris baru, membuat diff Anda tidak dapat digunakan. Perl sedikit lebih waras, dan ini adalah satu baris yang sangat bagus untuk pencarian dan penggantian. Terima kasih!
Andrew
@ Andrew, saya tidak yakin apa yang salah dalam kasus Anda. Akankah membantu Anda menggunakan \ R daripada \ n di regexps Anda? Lihat bagian \ R di perldoc.perl.org/perlrebackslash.html#Misc . Coba juga perldoc.perl.org/perlre.html#Regular-Expressions . Perl sangat lintas platform dan saya yakin ada cara bagi Anda untuk tidak berakhir dengan akhiran baris yang rusak.
Eric Johnson
Regex saya tidak memiliki baris baru di dalamnya, versi cygwin dari program ini secara otomatis ditambahkan di akhir setiap baris yang dimodifikasi. Saya secara khusus menyukai versi perl karena hanya memodifikasi baris yang cocok sedangkan sed akan memodifikasi semua baris, bahkan jika tidak cocok dengan regex.
Andrew
Bagaimana jika jalur file berisi spasi?
shengy
23

mungkin melakukan ini:

:noautocmd vim /Search/ **/*
:set hidden
:cfirst
qa
:%s//Replace/gce
:cnf
q
1000@a
:wa

Penjelasan:

  • :noautocmd vim /Search/ **/*⇒ lookup ( vimadalah singkatan dari vimgrep) pola di semua file di semua subdirektori cwd tanpa memicu autocmds ( :noautocmd), demi kecepatan.
  • :set hidden ⇒ biarkan buffer yang dimodifikasi tidak ditampilkan di jendela (bisa di vimrc Anda)
  • :cfirst ⇒ lompat ke hasil pencarian pertama
  • qa ⇒ mulai merekam makro ke register a
  • :%s//Replace/gce⇒ ganti semua kemunculan pola pencarian terakhir (masih /Search/pada saat itu) dengan Replace:
    • beberapa kali pada baris yang sama ( gbendera)
    • dengan konfirmasi pengguna ( cbendera)
    • tanpa kesalahan jika tidak ada pola yang ditemukan ( ebendera)
  • :cnf⇒ lompat ke file berikutnya dalam daftar yang dibuat oleh vimperintah
  • q ⇒ berhenti merekam makro
  • 1000@a ⇒ bermain makro disimpan dalam daftar 1000 kali
  • :wa ⇒ simpan semua buffer yang dimodifikasi

* EDIT * Vim 8 cara:

Dimulai dengan Vim 8, ada cara yang lebih baik untuk melakukannya, karena :cfdomengulangi semua file di daftar quickfix:

:noautocmd vim /Search/ **/*
:set hidden
:cfdo %s//Replace/gce
:wa
Benoit
sumber
Anda mungkin ingin menjelaskannya sedikit lebih jauh untuk kita yang lebih rendah. Tidak jelas bagi saya apakah Anda perlu melakukan "urutan" ini setiap saat. Saya lebih cepat melakukan itu menggunakan Delphi 5 lama saya yang berkarat jadi pasti saya kehilangan sesuatu.
Lieven Keersmaekers
1
@Lieven: Anda benar, ini agak tidak jelas tanpa komentar. Saya akan mengembangkan beberapa penjelasan.
Benoit
+1 tetapi meskipun vimtelah memenangkan saya untuk banyak hal, saya pikir saya akan tetap menggunakan PowerGrep dengan ini.
Lieven Keersmaekers
Terima kasih atas jawaban Anda dan penjelasan dari semua perintah, saya sangat menghargainya. Saya menerima jawaban lain karena saya lebih suka menggunakan plugin untuk ini.
psyho
Saya telah menandai ini karena ini menyebabkan lebih banyak kebingungan, mengingat perbedaan antara level seseorang yang mengajukan pertanyaan dan level yang diperlukan untuk memahami jawaban ini.
Lloyd Moore
22

Mengisi :argsdari perintah shell

Dimungkinkan (pada beberapa sistem operasi 1 )) untuk menyediakan file :argsmelalui perintah shell.

Misalnya, jika Anda menginstal ack 2 ,

:args `ack -l pattern`

akan meminta ack untuk mengembalikan daftar file yang berisi 'pola' dan meletakkannya di daftar argumen.

Atau dengan ol 'grep biasa saya kira itu akan menjadi:

:args `grep -lr pattern .`  


Anda kemudian dapat menggunakan :argdoseperti yang dijelaskan oleh OP:

:argdo %s/pattern/replacement/gce


Isi :argsdari daftar quickfix

Juga periksa jawaban nelstrom untuk pertanyaan terkait yang menjelaskan perintah sederhana yang ditentukan pengguna yang mengisi arglist dari daftar quickfix saat ini. Ini berfungsi dengan baik dengan banyak perintah dan plugin yang outputnya berakhir di daftar quickfix ( :vimgrep, :Ack3 , :Ggrep4 ).

Urutan untuk melakukan pencarian luas proyek kemudian dapat dilakukan dengan:

:vimgrep /pattern/ **/*
:Qargs 
:argdo %s/findme/replacement/gc

di mana :Qargspanggilan ke perintah yang ditentukan pengguna yang mengisi arglist dari daftar quickfix.

Anda juga akan menemukan tautan dalam diskusi berikutnya ke plugin sederhana yang membuat alur kerja ini turun menjadi 2 atau 3 perintah.

Tautan

  1. : h {arglist} - vimdoc.sourceforge.net/htmldoc/editing.html#{arglist}
  2. ack - betterthangrep.com/
  3. ack.vim - github.com/mileszs/ack.vim
  4. buronan - github.com/tpope/vim-fugitive
exbinary
sumber
1
argdo berhenti setelah file pertama dengan masalah tulis
frankster
9

Satu lagi opsi di 2016, plugin far.vim :

far.vom

Oleg Khalidov
sumber
4
Anda juga harus mengatakan bahwa Anda adalah pembuat plugin :)
sitilge
5

Jika Anda tidak keberatan memperkenalkan ketergantungan eksternal, saya telah membuat plugin ctrlsf.vim (tergantung pada ack atau ag ) untuk melakukan pekerjaan itu.

Itu dapat memformat dan menampilkan hasil pencarian dari ack / ag, dan menyinkronkan perubahan Anda dalam buffer hasil ke file aktual pada disk.

Mungkin demo berikut menjelaskan lebih lanjut

ctrlsf_edit_demo

dyng
sumber
3
1. :grep <search term> (or whatever you use to populate the quickfix window)
2. :cfdo %s/<search term>/<replace term>/g | update

Langkah 1 mengisi daftar quickfix dengan item yang Anda inginkan. Dalam hal ini, ini disebarkan dengan istilah penelusuran yang ingin Anda ubah grep.

cfdomenjalankan perintah berikut pada setiap file di daftar quickfix. Ketik :help cfdountuk detailnya.

s/<search term>/<replace term>/gmenggantikan setiap istilah. /gberarti mengganti setiap kejadian dalam file.

| update menyimpan file setelah setiap penggantian.

Saya mengumpulkan ini berdasarkan jawaban ini dan komentarnya, tetapi merasa itu pantas mendapatkan jawabannya sendiri karena semuanya ada di satu tempat.

Kyle Heironimus
sumber
Bagi saya di NeoVim, sedikit modifikasi perlu dilakukan pada baris 2 di atas: 2. :cfdo %s/<search term>/<replace term>/g | updateTanpa %, hanya pola kemunculan pertama yang diganti.
Kareem Ergawy
Terima kasih Kareem. Saya memperbarui jawabannya karena %sbekerja pada vim biasa juga.
Kyle Heironimus
0

Pada dasarnya, saya ingin mengganti dalam satu perintah dan yang lebih penting dalam vim itu sendiri

Berdasarkan jawaban oleh @Jefromi saya telah membuat pintasan keyboard, yang telah saya atur di file .vimrc saya seperti ini

nmap <leader>r :!grep -r -l  * \| xargs sed -i -e 's///g'

sekarang dari vim, dengan stroke pemimpin + r saya mendapatkan perintah dimuat di vim, yang saya edit seperti di bawah ini,

:!grep -r -l <find> <file pattern> | xargs sed -i -e 's/<find>/<replace>/g'

Oleh karena itu, saya melakukan penggantian dengan satu perintah dan yang lebih penting dalam vim itu sendiri

deepak
sumber
dan saya benar-benar menggunakan ag daripada grep
deepak