Thay đổi nhiều tập tin


194

Lệnh sau đây đang thay đổi chính xác nội dung của 2 tệp.

sed -i 's/abc/xyz/g' xaa1 xab1 

Nhưng điều tôi cần làm là thay đổi một số tệp như vậy một cách linh hoạt và tôi không biết tên tệp. Tôi muốn viết một lệnh sẽ đọc tất cả các tệp từ thư mục hiện tại bắt đầu bằng xa*sednên thay đổi nội dung tệp.


61
Ý bạn là sed -i 's/abc/xyz/g' xa*sao?
Paul R

3
Các câu trả lời ở đây không đủ. Xem unix.stackexchange.com/questions/112023/ấc
Isaac

Câu trả lời:


135

Tốt hơn nữa:

for i in xa*; do
    sed -i 's/asd/dfg/g' $i
done

bởi vì không ai biết có bao nhiêu tệp ở đó và thật dễ dàng để phá vỡ giới hạn dòng lệnh.

Đây là những gì xảy ra khi có quá nhiều tệp:

# grep -c aaa *
-bash: /bin/grep: Argument list too long
# for i in *; do grep -c aaa $i; done
0
... (output skipped)
#

18
Nếu có nhiều tệp như vậy, bạn sẽ phá vỡ giới hạn dòng lệnh trong forlệnh. Để bảo vệ bản thân khỏi điều đó, bạn phải sử dụngfind ... | xargs ...
glenn jackman

1
Tôi không biết việc triển khai, nhưng mẫu "xa *" phải được mở rộng tại một số điểm. Liệu shell làm việc mở rộng khác nhau cho forechohay cho grep?
glenn jackman

4
xem câu trả lời cập nhật nếu bạn cần thêm thông tin, xin vui lòng, hỏi một câu hỏi chính thức, để mọi người có thể giúp bạn.
lenik

5
Trong lệnh sed, bạn cần sử dụng "$i"thay vì $iđể tránh chia từ trên tên tệp có khoảng trắng. Nếu không thì điều này là rất tốt đẹp.
tự đại diện

4
Về danh sách, tôi tin rằng sự khác biệt forlà một phần của cú pháp ngôn ngữ, thậm chí không chỉ là nội dung. Đối với sed -i 's/old/new' *, việc mở rộng *TẤT CẢ phải được thông qua dưới dạng đối số cho sed và tôi khá chắc chắn điều này phải xảy ra trước khi sedquá trình thậm chí có thể được bắt đầu. Sử dụng forvòng lặp, đối số đầy đủ (mở rộng *) không bao giờ được chuyển qua dưới dạng lệnh, chỉ được lưu trữ trong bộ nhớ shell và lặp qua. Tôi không có bất kỳ tài liệu tham khảo nào cho việc này cả, có vẻ như đó là sự khác biệt. (Tôi muốn nghe từ một người hiểu biết hơn ...)
Wildcard

165

Tôi ngạc nhiên không ai đề cập đến đối số -exec cần tìm, được dành cho loại trường hợp sử dụng này, mặc dù nó sẽ bắt đầu một quy trình cho mỗi tên tệp phù hợp:

find . -type f -name 'xa*' -exec sed -i 's/asd/dsg/g' {} \;

Ngoài ra, người ta có thể sử dụng xargs, sẽ gọi ít quy trình hơn:

find . -type f -name 'xa*' | xargs sed -i 's/asd/dsg/g'

Hoặc đơn giản hơn là sử dụng + biến thể exec thay vì ;in find để cho phép find cung cấp nhiều hơn một tệp cho mỗi lệnh gọi quy trình con:

find . -type f -name 'xa*' -exec sed -i 's/asd/dsg/g' {} +

7
Tôi đã phải sửa đổi lệnh trong câu trả lời này như sau: find ./ -type f -name 'xa*' -exec sed -i '' 's/asd/dsg/g' {} \;đó là vị trí của lệnh find ./và một cặp dấu ngoặc đơn sau -icho OSX.
Shelbydz 17/03/2017

Lệnh find hoạt động vì nó được cung cấp bởi ealfonso, ./bằng .và sau -iđó chỉ có tham số backupsuffix.
uhausbrand

Các -exectùy chọn tìm cùng với {} +là đủ để giải quyết vấn đề như đã nêu, và nên sử dụng tốt cho hầu hết các yêu cầu này. Nhưng xargsnói chung là một lựa chọn tốt hơn vì nó cũng cho phép xử lý song song với -ptùy chọn. Khi mở rộng toàn cầu của bạn đủ lớn để vượt quá độ dài dòng lệnh của bạn, bạn có thể cũng được hưởng lợi từ việc tăng tốc trong một lần chạy liên tiếp.
Amit N Nikol

78

Bạn có thể sử dụng grep và sed cùng nhau. Điều này cho phép bạn tìm kiếm các thư mục con theo cách đệ quy.

Linux: grep -r -l <old> * | xargs sed -i 's/<old>/<new>/g'
OS X: grep -r -l <old> * | xargs sed -i '' 's/<old>/<new>/g'

For grep:
    -r recursively searches subdirectories 
    -l prints file names that contain matches
For sed:
    -i extension (Note: An argument needs to be provided on OS X)

3
Phần thưởng của phương pháp này đối với tôi là tôi có thể trượt vào grep -vđể tránh các thư mục gitgrep -rl <old> . | grep -v \.git | xargs sed -i 's/<old>/<new>/g'
Martin Lyne

giải pháp tốt nhất cho máy mac!
Hầu tước Blount

30

Các lệnh đó sẽ không hoạt động theo mặc định sedđi kèm với Mac OS X.

Từ man 1 sed:

-i extension
             Edit files in-place, saving backups with the specified
             extension.  If a zero-length extension is given, no backup 
             will be saved.  It is not recommended to give a zero-length
             extension when in-place editing files, as you risk corruption
             or partial content in situations where disk space is exhausted, etc.

Đã thử

sed -i '.bak' 's/old/new/g' logfile*

for i in logfile*; do sed -i '.bak' 's/old/new/g' $i; done

Cả hai đều hoạt động tốt.


2
@sumek Đây là một phiên thiết bị đầu cuối mẫu trên OS X cho thấy sed thay thế tất cả các lần xuất hiện: GitHub Gist
funroll

Tôi đã sử dụng điều này để thay thế hai dòng khác nhau trong tất cả các tệp cấu hình trang web của tôi bằng một lớp lót bên dưới. sed -i.bak "s / supercache_proxy_config / proxy_includes \ / supercache_config / g; s / basic_proxy_config / proxy_include \ / basic_proxy_config / g" các trang web có sẵn / * Đừng quên xóa các tệp * .bak hệ thống vệ sinh vì lợi ích.
Giô

19

@PaulR đăng bài này dưới dạng bình luận, nhưng mọi người nên xem nó như một câu trả lời (và câu trả lời này hoạt động tốt nhất cho nhu cầu của tôi):

sed -i 's/abc/xyz/g' xa*

Điều này sẽ làm việc cho một số lượng tệp vừa phải, có thể theo thứ tự hàng chục, nhưng có thể không theo thứ tự hàng triệu .


Giả sử bạn có dấu gạch chéo trong thay thế. Một ví dụ khác với filepaths sed -i 's|auth-user-pass nordvpn.txt|auth-user-pass /etc/openvpn/nordvpn.txt|g' *.ovpn.
Léo Léopold Hertz 준영

10

Một cách khác linh hoạt hơn là sử dụng find:

sed -i 's/asd/dsg/g' $(find . -type f -name 'xa*')

1
đầu ra của lệnh find được mở rộng, vì vậy điều này không giải quyết được vấn đề. Thay vào đó, bạn nên sử dụng -exec
ealfonso

@erjoalgo điều này hoạt động vì lệnh sed có thể xử lý nhiều tệp đầu vào. Việc mở rộng lệnh find chính xác là cần thiết để làm cho nó hoạt động.
dkinzer

nó hoạt động miễn là số lượng tệp không đẩy vào giới hạn dòng lệnh.
ealfonso

Giới hạn đó chỉ phụ thuộc vào tài nguyên bộ nhớ có sẵn cho máy và nó chính xác giống như giới hạn cho exec.
dkinzer

4
Đó chỉ đơn giản là không đúng sự thật. Trong lệnh của bạn ở trên, $ (find. ...) được mở rộng thành một lệnh duy nhất, có thể rất lâu nếu có nhiều tệp phù hợp. Nếu quá dài (ví dụ trong hệ thống của tôi, giới hạn là khoảng 2097152 ký tự), bạn có thể gặp lỗi: "Danh sách đối số quá dài" và lệnh sẽ thất bại. Vui lòng google lỗi này để có được một số nền tảng về điều này.
ealfonso

2

Tôi đang sử dụng findcho nhiệm vụ tương tự. Nó khá đơn giản: bạn phải vượt qua nó như một đối số cho sednhư thế này:

sed -i 's/EXPRESSION/REPLACEMENT/g' `find -name "FILE.REGEX"`

Bằng cách này, bạn không phải viết các vòng lặp phức tạp và thật đơn giản để xem, tệp nào bạn sẽ thay đổi, chỉ cần chạy findtrước khi bạn chạy sed.


1
Điều này hoàn toàn giống với câu trả lời của @ dkinzer .
Ông Tao

0

bạn có thể làm

' xxxx ' văn ​​bản tìm kiếm u và sẽ thay thế nó bằng ' yyyy '

grep -Rn '**xxxx**' /path | awk -F: '{print $1}' | xargs sed -i 's/**xxxx**/**yyyy**/'

0

Nếu bạn có thể chạy một kịch bản, đây là những gì tôi đã làm cho một tình huống tương tự:

Sử dụng một từ điển / hashMap (mảng kết hợp) và các biến cho sedlệnh, chúng ta có thể lặp qua mảng để thay thế một số chuỗi. Bao gồm một ký tự đại diện trong name_patternsẽ cho phép thay thế tại chỗ trong các tệp bằng một mẫu (đây có thể là một cái gì đó giống như name_pattern='File*.txt') trong một thư mục cụ thể ( source_dir). Tất cả các thay đổi được ghi trong logfiletrongdestin_dir

#!/bin/bash
source_dir=source_path
destin_dir=destin_path
logfile='sedOutput.txt'
name_pattern='File.txt'

echo "--Begin $(date)--" | tee -a $destin_dir/$logfile
echo "Source_DIR=$source_dir destin_DIR=$destin_dir "

declare -A pairs=( 
    ['WHAT1']='FOR1'
    ['OTHER_string_to replace']='string replaced'
)

for i in "${!pairs[@]}"; do
    j=${pairs[$i]}
    echo "[$i]=$j"
    replace_what=$i
    replace_for=$j
    echo " "
    echo "Replace: $replace_what for: $replace_for"
    find $source_dir -name $name_pattern | xargs sed -i "s/$replace_what/$replace_for/g" 
    find $source_dir -name $name_pattern | xargs -I{} grep -n "$replace_for" {} /dev/null | tee -a $destin_dir/$logfile
done

echo " "
echo "----End $(date)---" | tee -a $destin_dir/$logfile

Đầu tiên, mảng cặp được khai báo, mỗi cặp là một chuỗi thay thế, sau đó WHAT1sẽ được thay thế FOR1OTHER_string_to replacesẽ được thay thế string replacedtrong tệp File.txt. Trong vòng lặp, mảng được đọc, thành viên đầu tiên của cặp được lấy ra replace_what=$ivà thứ hai là replace_for=$j. Các findtìm kiếm lệnh trong thư mục tên tập tin (có thể chứa một ký tự đại diện) và sed -iThay thế lệnh trong cùng một file (s) những gì đã được định nghĩa trước. Cuối cùng tôi đã thêm một grepchuyển hướng vào logfile để ghi lại những thay đổi được thực hiện trong (các) tệp.

Điều này làm việc cho tôi GNU Bash 4.3 sed 4.2.2và dựa trên câu trả lời của VasyaNovikov cho Loop over tuples in bash .

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.