it-swarm-id.com

Bagaimana saya bisa mengganti string dalam file?

Mengganti string dalam file berdasarkan kriteria pencarian tertentu adalah tugas yang sangat umum. Bagaimana bisa saya

  • ganti string foo dengan bar di semua file di direktori saat ini?
  • melakukan hal yang sama secara rekursif untuk sub direktori?
  • ganti hanya jika nama file cocok dengan string lain?
  • ganti hanya jika string ditemukan dalam konteks tertentu?
  • ganti jika string ada pada nomor baris tertentu?
  • ganti beberapa string dengan penggantian yang sama
  • ganti banyak string dengan penggantian yang berbeda
791
terdon

1. Mengganti semua kemunculan satu string dengan yang lain di semua file di direktori saat ini:

Ini adalah untuk kasus di mana Anda tahu bahwa direktori hanya berisi file biasa dan Anda ingin memproses semua file yang tidak disembunyikan. Jika bukan itu masalahnya, gunakan pendekatan dalam 2.

Semua solusi sed dalam jawaban ini mengasumsikan GNU sed. Jika menggunakan FreeBSD atau OS/X, ganti -i dengan -i ''. Juga catat bahwa penggunaan sakelar -i dengan versi sed apa saja memiliki sistem berkas tertentu implikasi keamanan dan tidak disarankan dalam skrip apa pun yang Anda rencanakan untuk didistribusikan di bagaimanapun juga.

  • Non rekursif, hanya file dalam direktori ini:

    sed -i -- 's/foo/bar/g' *
    Perl -i -pe 's/foo/bar/g' ./* 
    

    (the Perl seseorang akan gagal untuk nama file yang diakhiri dengan | atau spasi) ).

  • File rekursif, reguler ( termasuk yang tersembunyi ) di ini dan semua subdirektori

    find . -type f -exec sed -i 's/foo/bar/g' {} +
    

    Jika Anda menggunakan zsh:

    sed -i -- 's/foo/bar/g' **/*(D.)
    

    (mungkin gagal jika daftar terlalu besar, lihat zargs untuk bekerja di sekitar).

    Bash tidak dapat memeriksa langsung untuk file biasa, diperlukan loop (kawat gigi menghindari pengaturan opsi secara global):

    ( shopt -s globstar dotglob;
        for file in **; do
            if [[ -f $file ]] && [[ -w $file ]]; then
                sed -i -- 's/foo/bar/g' "$file"
            fi
        done
    )
    

    File-file dipilih ketika mereka adalah file aktual (-f) dan mereka dapat ditulis (-w).

2. Ganti hanya jika nama file cocok dengan string lain/memiliki ekstensi spesifik/jenis tertentu dll:

  • Non-rekursif, hanya file dalam direktori ini:

    sed -i -- 's/foo/bar/g' *baz*    ## all files whose name contains baz
    sed -i -- 's/foo/bar/g' *.baz    ## files ending in .baz
    
  • File rekursif dan teratur dalam subdirektori ini dan semua

    find . -type f -name "*baz*" -exec sed -i 's/foo/bar/g' {} +
    

    Jika Anda menggunakan bash (kawat gigi hindari pengaturan opsi secara global):

    ( shopt -s globstar dotglob
        sed -i -- 's/foo/bar/g' **baz*
        sed -i -- 's/foo/bar/g' **.baz
    )
    

    Jika Anda menggunakan zsh:

    sed -i -- 's/foo/bar/g' **/*baz*(D.)
    sed -i -- 's/foo/bar/g' **/*.baz(D.)
    

    -- berfungsi untuk memberi tahu sed bahwa tidak ada lagi flag yang akan diberikan pada baris perintah. Ini berguna untuk melindungi terhadap nama file yang dimulai dengan -.

  • Jika suatu file berjenis tertentu, misalnya, dapat dieksekusi (lihat man find untuk opsi lebih lanjut):

    find . -type f -executable -exec sed -i 's/foo/bar/g' {} +
    

    zsh:

    sed -i -- 's/foo/bar/g' **/*(D*)
    

3. Ganti hanya jika string ditemukan dalam konteks tertentu

  • Ganti foo dengan bar hanya jika ada baz nanti di baris yang sama:

    sed -i 's/foo\(.*baz\)/bar\1/' file
    

    Dalam sed, menggunakan \( \) menyimpan apa pun yang ada di dalam tanda kurung dan Anda kemudian dapat mengaksesnya dengan \1. Ada banyak variasi tema ini, untuk mempelajari lebih lanjut tentang ekspresi reguler seperti itu, lihat di sini .

  • Ganti foo dengan bar hanya jika foo ditemukan pada kolom 3d (bidang) dari file input (dengan asumsi bidang yang dipisahkan spasi-putih):

    gawk -i inplace '{gsub(/foo/,"baz",$3); print}' file
    

    (kebutuhan gawk 4.1.0 atau lebih baru).

  • Untuk bidang yang berbeda cukup gunakan $N di mana N adalah jumlah bidang yang diminati. Untuk pemisah bidang yang berbeda (: dalam contoh ini) gunakan:

    gawk -i inplace -F':' '{gsub(/foo/,"baz",$3);print}' file
    

    Solusi lain menggunakan Perl:

    Perl -i -ane '$F[2]=~s/foo/baz/g; $" = " "; print "@F\n"' foo 
    

    CATATAN: solusi awk dan Perl akan memengaruhi penspasian dalam file (hapus blanko terkemuka dan akhir, dan ubah urutan blanko menjadi satu karakter spasi dalam baris yang cocok). Untuk bidang yang berbeda, gunakan $F[N-1] di mana N adalah nomor bidang yang Anda inginkan dan untuk penggunaan pemisah bidang yang berbeda ($"=":" mengatur pemisah bidang keluaran ke : ):

    Perl -i -F':' -ane '$F[2]=~s/foo/baz/g; $"=":";print "@F"' foo 
    
  • Ganti foo dengan bar hanya di baris ke-4:

    sed -i '4s/foo/bar/g' file
    gawk -i inplace 'NR==4{gsub(/foo/,"baz")};1' file
    Perl -i -pe 's/foo/bar/g if $.==4' file
    

4. Beberapa operasi penggantian: ganti dengan string yang berbeda

  • Anda dapat menggabungkan perintah sed:

    sed -i 's/foo/bar/g; s/baz/zab/g; s/Alice/Joan/g' file
    

    Ketahuilah bahwa masalah pesanan (sed 's/foo/bar/g; s/bar/baz/g' akan menggantikan foo dengan baz).

  • atau perintah Perl

    Perl -i -pe 's/foo/bar/g; s/baz/zab/g; s/Alice/Joan/g' file
    
  • Jika Anda memiliki banyak pola, lebih mudah untuk menyimpan pola dan penggantiannya dalam file skrip sed:

    #! /usr/bin/sed -f
    s/foo/bar/g
    s/baz/zab/g
    
  • Atau, jika Anda memiliki terlalu banyak pasangan pola untuk dapat di atas, Anda dapat membaca pasangan pola dari file (dua pola yang dipisahkan spasi, $ pola dan $ penggantian, per baris):

    while read -r pattern replacement; do   
        sed -i "s/$pattern/$replacement/" file
    done < patterns.txt
    
  • Itu akan sangat lambat untuk daftar panjang pola dan file data besar sehingga Anda mungkin ingin membaca pola dan membuat skrip sed sebagai gantinya. Berikut ini mengasumsikan <space> pembatas memisahkan daftar MATCH <space> REPLACE pasangan yang muncul satu per baris dalam file patterns.txt:

    sed 's| *\([^ ]*\) *\([^ ]*\).*|s/\1/\2/g|' <patterns.txt |
    sed -f- ./editfile >outfile
    

    Format di atas sebagian besar arbitrer dan, misalnya, tidak memungkinkan untuk <spasi> dalam salah satu dari [~ # ~] cocok dengan [~ # ~] atau [~ # ~] ganti [~ # ~]. Metode ini sangat umum: pada dasarnya, jika Anda dapat membuat aliran output yang terlihat seperti skrip sed, maka Anda dapat sumber stream tersebut sebagai skrip sed dengan menentukan sed sebagai -stdin.

  • Anda dapat menggabungkan dan menggabungkan beberapa skrip dengan cara yang sama:

    SOME_PIPELINE |
    sed -e'#some expression script'  \
        -f./script_file -f-          \
        -e'#more inline expressions' \
    ./actual_edit_file >./outfile
    

    POSIX sed akan menggabungkan semua skrip menjadi satu sesuai dengan urutannya pada baris perintah. Tidak satu pun dari ini yang perlu diakhiri dengan \newline.

  • grep dapat bekerja dengan cara yang sama:

    sed -e'#generate a pattern list' <in |
    grep -f- ./grepped_file
    
  • Saat bekerja dengan string-tetap sebagai pola, adalah praktik yang baik untuk menghindari ekspresi reguler metacharacters. Anda dapat melakukan ini dengan agak mudah:

    sed 's/[]$&^*\./[]/\\&/g
         s| *\([^ ]*\) *\([^ ]*\).*|s/\1/\2/g|
    ' <patterns.txt |
    sed -f- ./editfile >outfile
    

5. Beberapa operasi penggantian: ganti beberapa pola dengan string yang sama

  • Ganti foo, bar atau baz dengan foobar

    sed -Ei 's/foo|bar|baz/foobar/g' file
    
  • atau

    Perl -i -pe 's/foo|bar|baz/foobar/g' file
    
1061
terdon

Alat r e pl Linux yang bagus adalah rpl, yang awalnya ditulis untuk proyek Debian, jadi tersedia dengan apt-get install rpl di setiap distro turunan Debian, dan mungkin untuk orang lain, tetapi jika tidak Anda dapat mengunduh tar.gz file dalam SourgeForge .

Contoh penggunaan paling sederhana:

 $ rpl old_string new_string test.txt

Perhatikan bahwa jika string berisi spasi, ia harus dilampirkan dalam tanda kutip. Secara default rpl mengurus huruf kapital tetapi tidak dari kata lengkap , tetapi Anda dapat mengubah default ini dengan opsi -i (abaikan kasing) dan -w (seluruh kata). Anda juga dapat menentukan beberapa file :

 $ rpl -i -w "old string" "new string" test.txt test2.txt

Atau bahkan menentukan ekstensi (-x) untuk mencari atau bahkan mencari secara rekursif (-R) dalam direktori:

 $ rpl -x .html -x .txt -R old_string new_string test*

Anda juga dapat mencari/mengganti dalam mode interaktif dengan -p Pilihan (Prompt):

Outputnya menunjukkan jumlah file/string yang diganti dan jenis pencarian (huruf dalam/sensitif, seluruh/sebagian kata), tetapi bisa diam dengan -q ( opsi mode diam ), atau lebih banyak lagi, daftar nomor baris yang berisi kecocokan dari setiap file dan direktori dengan -v ( pilihan mode verbose ).

Opsi lain yang patut diingat adalah -e (honor e scapes) yang memungkinkan regular expressions, jadi Anda juga dapat mencari tab (\t), baris baru (\n), dll. Bahkan Anda bisa menggunakan -f to memaksa izin (tentu saja, hanya ketika pengguna memiliki izin menulis) dan -d untuk mempertahankan waktu modifikasi`).

Akhirnya, jika Anda tidak yakin yang mana yang akan membuatnya persis, gunakan -s ( mode simulasi ).

79
Fran

Cara melakukan pencarian dan mengganti beberapa file menyarankan:

Anda juga bisa menggunakan find dan sed, tetapi saya menemukan bahwa garis kecil Perl ini bekerja dengan baik.

Perl -pi -w -e 's/search/replace/g;' *.php
  • -e berarti menjalankan baris kode berikut.
  • -i berarti mengedit di tempat
  • -w menulis peringatan
  • -p loop di atas file input, mencetak setiap baris setelah script diterapkan padanya.

Hasil terbaik saya berasal dari menggunakan Perl dan grep (untuk memastikan file memiliki ekspresi pencarian)

Perl -pi -w -e 's/search/replace/g;' $( grep -rl 'search' )

Saya menggunakan ini:

grep -r "old_string" -l | tr '\n' ' ' | xargs sed -i 's/old_string/new_string/g'
  1. Daftar semua file yang mengandung old_string.

  2. Ganti baris baru dalam hasil dengan spasi (sehingga daftar file dapat diumpankan ke sed.

  3. Jalankan sed pada file-file itu untuk mengganti string lama dengan yang baru.

Perbarui: Hasil di atas akan gagal pada nama file yang berisi spasi putih. Sebaliknya, gunakan:

grep --null -lr "old_string" | xargs --null sed -i 's/old_string/new_string/g'

15
o_o_o--

Anda dapat menggunakan Vim dalam mode Ex:

ganti string ALF dengan BRA di semua file di direktori saat ini?

for CHA in *
do
  ex -sc '%s/ALF/BRA/g' -cx "$CHA"
done

melakukan hal yang sama secara rekursif untuk sub direktori?

find -type f -exec ex -sc '%s/ALF/BRA/g' -cx {} ';'

ganti hanya jika nama file cocok dengan string lain?

for CHA in *.txt
do
  ex -sc '%s/ALF/BRA/g' -cx "$CHA"
done

ganti hanya jika string ditemukan dalam konteks tertentu?

ex -sc 'g/DEL/s/ALF/BRA/g' -cx file

ganti jika string ada pada nomor baris tertentu?

ex -sc '2s/ALF/BRA/g' -cx file

ganti beberapa string dengan penggantian yang sama

ex -sc '%s/\vALF|ECH/BRA/g' -cx file

ganti banyak string dengan penggantian yang berbeda

ex -sc '%s/ALF/BRA/g|%s/FOX/GOL/g' -cx file
15
Steven Penny

Dari perspektif pengguna, alat Unix yang bagus & sederhana yang melakukan pekerjaan dengan sempurna adalah qsubst . Sebagai contoh,

% qsubst foo bar *.c *.h

akan mengganti foo dengan bar di semua file C saya. Fitur yang bagus adalah qsubst akan melakukan ganti-permintaan , yaitu, ia akan menunjukkan kepada saya setiap kemunculan foo dan tanyakan apakah saya ingin menggantinya atau tidak. [Anda dapat mengganti tanpa syarat (tanpa bertanya) dengan -go opsi, dan ada opsi lain, mis., -w jika Anda hanya ingin mengganti foo saat itu adalah keseluruhan Word.]

Cara mendapatkannya: qsubst ditemukan oleh der Mouse (dari McGill) dan diposting ke comp.unix.sources 11 (7) pada Agustus 1987. Versi yang diperbarui ada. Misalnya, versi NetBSD qsubst.c,v 1.8 2004/11/01 mengkompilasi dan berjalan dengan sempurna di mac saya.

7
phs

ripgrep (nama perintah rg) adalah alat grep, tetapi mendukung pencarian dan penggantian juga.

$ cat ip.txt
dark blue and light blue
light orange
blue sky
$ # by default, line number is displayed if output destination is stdout
$ # by default, only lines that matched the given pattern is displayed
$ # 'blue' is search pattern and -r 'red' is replacement string
$ rg 'blue' -r 'red' ip.txt
1:dark red and light red
3:red sky

$ # --passthru option is useful to print all lines, whether or not it matched
$ # -N will disable line number prefix
$ # this command is similar to: sed 's/blue/red/g' ip.txt
$ rg --passthru -N 'blue' -r 'red' ip.txt
dark red and light red
light orange
red sky


rg tidak mendukung opsi di tempat, jadi Anda harus melakukannya sendiri

$ # -N isn't needed here as output destination is a file
$ rg --passthru 'blue' -r 'red' ip.txt > tmp.txt && mv tmp.txt ip.txt
$ cat ip.txt
dark red and light red
light orange
red sky


Lihat Dokumentasi regex Rust untuk sintaks dan fitur ekspresi reguler. -P switch akan mengaktifkan PCRE2 rasa. rg mendukung Unicode secara default.

$ # non-greedy quantifier is supported
$ echo 'food land bark sand band cue combat' | rg 'foo.*?ba' -r 'X'
Xrk sand band cue combat

$ # unicode support
$ echo 'fox:αλεπού,eagle:αετός' | rg '\p{L}+' -r '($0)'
(fox):(αλεπού),(eagle):(αετός)

$ # set operator example, remove all punctuation characters except . ! and ?
$ para='"Hi", there! How *are* you? All fine here.'
$ echo "$para" | rg '[[:punct:]--[.!?]]+' -r ''
Hi there! How are you? All fine here.

$ # use -P if you need even more advanced features
$ echo 'car bat cod map' | rg -P '(bat|map)(*SKIP)(*F)|\w+' -r '[$0]'
[car] bat [cod] map


Seperti grep, -F opsi akan memungkinkan string tetap untuk dicocokkan, opsi praktis yang saya rasa sed harus diterapkan juga.

$ printf '2.3/[4]*6\nfoo\n5.3-[4]*9\n' | rg --passthru -F '[4]*' -r '2'
2.3/26
foo
5.3-29


Pilihan praktis lainnya adalah -U yang memungkinkan pencocokan multiline

$ # (?s) flag will allow . to match newline characters as well
$ printf '42\nHi there\nHave a Nice Day' | rg --passthru -U '(?s)the.*ice' -r ''
42
Hi  Day


rg juga dapat menangani file gaya dos

$ # same as: sed -E 's/\w+(\r?)$/123\1/'
$ printf 'hi there\r\ngood day\r\n' | rg --passthru --crlf '\w+$' -r '123'
hi 123
good 123


Keuntungan lain dari rg adalah kemungkinannya lebih cepat dari sed

$ # for small files, initial processing time of rg is a large component
$ time echo 'aba' | sed 's/a/b/g' > f1
real    0m0.002s
$ time echo 'aba' | rg --passthru 'a' -r 'b' > f2
real    0m0.007s

$ # for larger files, rg is likely to be faster
$ # 6.2M sample ASCII file
$ wget https://norvig.com/big.txt    
$ time LC_ALL=C sed 's/\bcat\b/dog/g' big.txt > f1
real    0m0.060s
$ time rg --passthru '\bcat\b' -r 'dog' big.txt > f2
real    0m0.048s
$ diff -s f1 f2
Files f1 and f2 are identical

$ time LC_ALL=C sed -E 's/\b(\w+)(\s+\1)+\b/\1/g' big.txt > f1
real    0m0.725s
$ time rg --no-pcre2-unicode --passthru -wP '(\w+)(\s+\1)+' -r '$1' big.txt > f2
real    0m0.093s
$ diff -s f1 f2
Files f1 and f2 are identical
3
Sundeep

Saya membutuhkan sesuatu yang akan memberikan opsi dry-run dan akan bekerja secara rekursif dengan glob, dan setelah mencoba melakukannya dengan awk dan sed Saya menyerah dan malah melakukannya dengan python.

The script mencari secara rekursif semua file yang cocok dengan pola glob (mis. --glob="*.html") untuk regex dan diganti dengan regex pengganti:

find_replace.py [--dir=my_folder] \
    --search-regex=<search_regex> \
    --replace-regex=<replace_regex> \
    --glob=[glob_pattern] \
    --dry-run

Setiap opsi panjang seperti --search-regex memiliki opsi pendek yang sesuai, mis. -s. Jalankan dengan -h untuk melihat semua opsi.

Misalnya, ini akan membalik semua tanggal dari 2017-12-31 hingga 31-12-2017:

python replace.py --glob=myfile.txt \
    --search-regex="(\d{4})-(\d{2})-(\d{2})" \
    --replace-regex="\3-\2-\1" \
    --dry-run --verbose
import os
import fnmatch
import sys
import shutil
import re

import argparse

def find_replace(cfg):
    search_pattern = re.compile(cfg.search_regex)

    if cfg.dry_run:
        print('THIS IS A DRY RUN -- NO FILES WILL BE CHANGED!')

    for path, dirs, files in os.walk(os.path.abspath(cfg.dir)):
        for filename in fnmatch.filter(files, cfg.glob):

            if cfg.print_parent_folder:
                pardir = os.path.normpath(os.path.join(path, '..'))
                pardir = os.path.split(pardir)[-1]
                print('[%s]' % pardir)
            filepath = os.path.join(path, filename)

            # backup original file
            if cfg.create_backup:
                backup_path = filepath + '.bak'

                while os.path.exists(backup_path):
                    backup_path += '.bak'
                print('DBG: creating backup', backup_path)
                shutil.copyfile(filepath, backup_path)

            with open(filepath) as f:
                old_text = f.read()

            all_matches = search_pattern.findall(old_text)

            if all_matches:

                print('Found {} matches in file {}'.format(len(all_matches), filename))

                new_text = search_pattern.sub(cfg.replace_regex, old_text)

                if not cfg.dry_run:
                    with open(filepath, "w") as f:
                        print('DBG: replacing in file', filepath)
                        f.write(new_text)
                else:
                    for idx, matches in enumerate(all_matches):
                        print("Match #{}: {}".format(idx, matches))

                    print("NEW TEXT:\n{}".format(new_text))

            Elif cfg.verbose:
                print('File {} does not contain search regex "{}"'.format(filename, cfg.search_regex))


if __name__ == '__main__':

    parser = argparse.ArgumentParser(description='''DESCRIPTION:
    Find and replace recursively from the given folder using regular expressions''',
                                     formatter_class=argparse.RawDescriptionHelpFormatter,
                                     epilog='''USAGE:
    {0} -d [my_folder] -s <search_regex> -r <replace_regex> -g [glob_pattern]

    '''.format(os.path.basename(sys.argv[0])))

    parser.add_argument('--dir', '-d',
                        help='folder to search in; by default current folder',
                        default='.')

    parser.add_argument('--search-regex', '-s',
                        help='search regex',
                        required=True)

    parser.add_argument('--replace-regex', '-r',
                        help='replacement regex',
                        required=True)

    parser.add_argument('--glob', '-g',
                        help='glob pattern, i.e. *.html',
                        default="*.*")

    parser.add_argument('--dry-run', '-dr',
                        action='store_true',
                        help="don't replace anything just show what is going to be done",
                        default=False)

    parser.add_argument('--create-backup', '-b',
                        action='store_true',
                        help='Create backup files',
                        default=False)

    parser.add_argument('--verbose', '-v',
                        action='store_true',
                        help="Show files which don't match the search regex",
                        default=False)

    parser.add_argument('--print-parent-folder', '-p',
                        action='store_true',
                        help="Show the parent info for debug",
                        default=False)

    config = parser.parse_args(sys.argv[1:])

    find_replace(config)

Here adalah versi terbaru dari skrip yang menyoroti istilah pencarian dan penggantian dengan warna yang berbeda.

3
ccpizza