Làm cách nào tôi có thể đạt được tính di động với sed -i (chỉnh sửa tại chỗ)?


40

Tôi đang viết kịch bản shell cho máy chủ của mình, đây là một dịch vụ lưu trữ được chia sẻ đang chạy FreeBSD. Tôi cũng muốn có thể kiểm tra chúng cục bộ, trên PC của tôi chạy Linux. Do đó, tôi đang cố gắng viết chúng theo cách di động, nhưng với sedtôi thấy không có cách nào để làm điều đó.

Một phần của trang web của tôi sử dụng các tệp HTML tĩnh được tạo và dòng sed này chèn DOCTYPE chính xác sau mỗi lần tái tạo:

sed -i '1s/^/<!DOCTYPE html> \n/' ${file_name.html}

Nó hoạt động với GNU sedtrên Linux, nhưng FreeBSD sedhy vọng đối số đầu tiên sau -itùy chọn là phần mở rộng cho bản sao lưu. Đây là cách nó sẽ trông như thế nào:

sed -i '' '1s/^/<!DOCTYPE html> \n/' ${file_name.html}

Tuy nhiên, GNU sedlần lượt hy vọng biểu thức sẽ theo sau ngay sau đó -i. (Nó cũng yêu cầu sửa lỗi với xử lý dòng mới, nhưng điều đó đã được trả lời ở đây )

Tất nhiên tôi có thể bao gồm thay đổi này trong bản sao máy chủ của tập lệnh, nhưng điều đó sẽ gây rối, tức là việc tôi sử dụng VCS để tạo phiên bản. Có cách nào để đạt được điều này với sed theo cách hoàn toàn di động không?


Hai đoạn trích sed bạn cung cấp giống hệt nhau, bạn có chắc là không có lỗi đánh máy không? Ngoài ra, tôi có thể thực thi GNU sed cung cấp tiện ích mở rộng sao lưu ngay sau đó-i
iruvar

Duh, cảm ơn vì đã phát hiện ra điều này. Tôi đã sửa câu hỏi của mình. Dòng thứ hai dẫn đến lỗi trong sed của tôi, nó hy vọng '1s / ^ / <! DOCTYPE html> \ n /' là một tệp và phàn nàn rằng nó không thể tìm thấy nó.
Đỏ

Câu trả lời:


39

GNU sed chấp nhận một phần mở rộng tùy chọn sau -i. Phần mở rộng phải trong cùng một đối số không có không gian can thiệp. Cú pháp này cũng hoạt động trên BSD sed.

sed -i.bak -e '…' SOMEFILE

Lưu ý rằng trên BSD, -icũng thay đổi hành vi khi có nhiều tệp đầu vào: chúng được xử lý độc lập (ví dụ: $khớp với dòng cuối cùng của mỗi tệp). Ngoài ra, điều này sẽ không hoạt động trên BusyBox.

Nếu bạn không muốn sử dụng các tập tin sao lưu, bạn có thể kiểm tra phiên bản sed nào có sẵn.

case $(sed --help 2>&1) in
  *GNU*) set sed -i;;
  *) set sed -i '';;
esac
"$@" -e '…' "$file"

Hoặc cách khác, để tránh ghi đè các tham số vị trí, xác định hàm.

case $(sed --help 2>&1) in
  *GNU*) sed_i () { sed -i "$@"; };;
  *) sed_i () { sed -i '' "$@"; };;
esac
sed_i -e '…' "$file"

Nếu bạn không muốn làm phiền, hãy sử dụng Perl.

perl -i -pe '…' "$file"

Nếu bạn muốn viết một tập lệnh di động, đừng sử dụng -i- nó không có trong POSIX. Làm thủ công những gì sed làm dưới mui xe - đó chỉ là một dòng mã nữa.

sed -e '…' "$file" >"$file.new"
mv -- "$file.new" "$file"

2
GNU sed -icũng ngụ ý -s. Và cách dễ nhất để kiểm tra GNU sedlà với sed vlệnh noop hợp lệ cho GNU nhưng không thành công ở mọi nơi khác.
mikeerv

Lấy cảm hứng từ những lời khuyên ở trên, đây là một single-line (nếu xấu xí) phiên bản di động cho những ai thực sự muốn một, mặc dù nó không đẻ trứng một subshell: sed -i$(sed v < /dev/null 2> /dev/null || echo -n " ''") -e '...' "$file"Nếu nó không phải GNU sed, nó chèn một không gian sau đó là hai dấu ngoặc kép singe sau khi -iđể nó hoạt động trên BSD. GNU sed chỉ được -i.
Ivan X

2
@IvanX Tôi cảnh giác khi sử dụng sự hiện diện của vlệnh để kiểm tra GNU sed. Điều gì xảy ra nếu FreeBSD quyết định thực hiện nó?
Gilles 'SO- ngừng trở nên xấu xa'

@Gilles Điểm công bằng, nhưng trang man cho GNU sed mô tả v là chính xác cho mục đích đó (phát hiện ra rằng đó là GNU sed chứ không phải cái gì khác), vì vậy người ta sẽ hy vọng rằng * BSD sẽ tôn vinh điều đó. Tôi không thể nghĩ ra một thử nghiệm khác, một cách trực tiếp, không có hành động nào đối với GNU sed, trong khi gây ra lỗi trên BSD sed (hoặc ngược lại), ngoài việc sử dụng -i, nhưng trước tiên sẽ yêu cầu tạo một tệp giả. Thử nghiệm của bạn cho sed ở trên là OK nhưng khó sử dụng cho nội tuyến. Tránh hoàn toàn - như bạn đề xuất, chắc chắn có vẻ như là đặt cược an toàn nhất, nhưng tôi vẫn ổn với việc sử dụng sed vmục đích đó là tồn tại.
Ivan X

1
@lapo Một cách khác là xác định hàm, xem phần chỉnh sửa của tôi.
Gilles 'SO- ngừng trở thành ác quỷ'

10

Nếu bạn không tìm thấy một mẹo để sedchơi hay, bạn có thể thử:

  1. Đừng sử dụng -i:

    sed '1s/^/<!DOCTYPE html> \n/' "${file_name.html}" > "${file_name.html}.tmp" &&
      mv "${file_name.html}.tmp" "${file_name.html}"
    
  2. Sử dụng Perl

    perl -i -pe 'print "<!DOCTYPE html> \n" if $.==1;' "${file_name.html}"

8

ed

Bạn luôn có thể sử dụng edđể thêm một dòng vào một tệp hiện có.

$ printf '0a\n<!DOCTYPE html>\n.\nw\n' | ed my.html

Chi tiết

Các bit xung quanh <!DOCTYPE html>là các lệnh để edhướng dẫn nó thêm dòng đó vào tệp my.html.

quyến rũ

Tôi tin rằng lệnh này sedcũng có thể được sử dụng:

$ sed -i '1i<!DOCTYPE html>\n` testfile.csv

Cuối cùng tôi đã dùng đến Perl, nhưng sử dụng ed là một lựa chọn tốt không phổ biến đối với người dùng giống Unix như nó nên có.
Đỏ

@Red - rất vui khi biết bạn giải quyết vấn đề. Vâng, tôi chưa từng thấy cái đó trước đây, googling đã bật nó lên và nó thực sự có vẻ như là cách di động nhất, thích hợp nhất để làm điều này.
slm

7

Bạn cũng có thể làm thủ công những gì perl -ilàm dưới mui xe:

{ rm -f file && { echo '<!DOCTYPE html>'; cat; } > file;} < file

Giống như perl -i, không có bản sao lưu và giống như hầu hết các giải pháp được đưa ra ở đây, hãy coi chừng nó có thể ảnh hưởng đến quyền, quyền sở hữu tệp và có thể biến một liên kết tượng trưng thành một tệp thông thường.

Với:

sed '1i\
<!DOCTYPE html>' file 1<> file

sedsẽ ghi đè lên tệp, vì vậy sẽ không ảnh hưởng đến quyền sở hữu và quyền hoặc liên kết tượng trưng. Nó hoạt động với GNU sedsedthông thường sẽ đọc một bộ đệm chứa đầy dữ liệu từ file(4k trong trường hợp của tôi) trước khi ghi đè lên nó bằng ilệnh. Điều đó sẽ không hoạt động nếu tệp có hơn 4k ngoại trừ thực tế là sedcũng đệm đầu ra của nó.

Về cơ bản sedhoạt động trên các khối 4k để đọc và viết. Nếu dòng cần chèn nhỏ hơn 4k, sedsẽ không bao giờ ghi đè lên khối mà nó chưa đọc.

Tôi sẽ không tin vào điều đó.


Nên echo '<!DOCTYPE html>'hoặc thoát mà không có dấu ngoặc kép "".
AD

@AD Điểm tốt. Tôi có xu hướng quên đi lỗi đó ^ Wfeature của bash / zsh tương tác vì tôi thường vô hiệu hóa nó cho chính mình.
Stéphane Chazelas

Đây là một trong số rất ít câu trả lời ở đây không có lỗ hổng bảo mật mở rộng khi chuyển hướng đến "tệp tạm thời" với tên tĩnh, có thể dự đoán được mà không kiểm tra xem nó đã tồn tại chưa. Tôi tin rằng đây nên là câu trả lời được chấp nhận. Trình diễn rất hay về việc sử dụng các lệnh nhóm, cũng có.
tự đại diện

3

FreeBSD sed , cũng được sử dụng trên Mac OS X, cần -etùy chọn sau khi -ichuyển đổi để xác định và nhận ra lệnh (regex) sau đây một cách chính xác & rõ ràng.

Nói cách khác, sed -i -e ...nên hoạt động với cả FreeBSD & GNU sed.

Tổng quát hơn, bỏ qua phần mở rộng sao lưu sau FreeBSD sed -iyêu cầu một số sedtùy chọn rõ ràng hoặc chuyển đổi theo sau -iđể tránh nhầm lẫn về một phần của FreeBSD sedtrong khi phân tích các đối số dòng lệnh của nó.

(Tuy nhiên, lưu ý rằng sedchỉnh sửa tệp tại chỗ dẫn đến thay đổi inode tệp, xem chỉnh sửa tệp "tại chỗ" ).

(Như một gợi ý chung, các phiên bản gần đây của FreeBSD sedcó công -rtắc để tăng khả năng tương thích với GNU sed).

echo a > testfile.txt
ls -li testfile.txt
#gsed -i -e 's/a/A/' testfile.txt
#bsdsed -i 's/a/A/' testfile.txt  # does not work
bsdsed -i -e 's/a/A/' testfile.txt
ls -li testfile.txt
cat testfile.txt

5
Không, không phảibsdsed -i -e 's/a/A/' là chỉnh sửa tại chỗ, đó là chỉnh sửa bằng cách lưu bản gốc với hậu tố "-e" ( ). testfile.txt-e
Stéphane Chazelas

lưu ý rằng GNU sed cũng hỗ trợ -E (ngoài -r) để tương thích với FreeBSD. -E có khả năng được chỉ định trong phiên bản POSIX tiếp theo, vì vậy tất cả chúng ta nên quên đi điều đó -r không có ý nghĩa và giả vờ nó không bao giờ tồn tại.
Stéphane Chazelas

2

Để mô phỏng sed -icho một tệp duy nhất có thể di chuyển trong khi tránh các điều kiện cuộc đua càng nhiều càng tốt:

sed 'script' <<FILE >file
$(cat file)
FILE

Nhân tiện, điều này cũng xử lý vấn đề có thể xảy sed -ira trong đó, tùy thuộc vào quyền của thư mục và tệp, sed -icó thể cho phép người dùng ghi đè lên tệp mà người dùng không có quyền chỉnh sửa.

Bạn cũng có thể thực hiện sao lưu như:

sed 'script' <<FILE >file
$(tee file.old <file)
FILE

2

Bạn có thể sử dụng Vim trong chế độ Ex:

ex -sc '1i|<!DOCTYPE html>' -cx file
  1. 1 chọn dòng đầu tiên

  2. i chèn văn bản và dòng mới

  3. x lưu và đóng

Hoặc thậm chí, như trong các ý kiến, tiêu chuẩn ex cũ đơn giản :

printf '%s\n' 1i '<!DOCTYPE html>' . x | ex file 

Không có gì cụ thể ở đây. Điều này hoàn toàn tuân thủ các thông số kỹ thuật của POSIXex , ngoại trừ việc triển khai không bắt buộc để hỗ trợ nhiều -ccờ. Đối với tính di động nhất định tôi sẽ sử dụngprintf '%s\n' 1i '<!DOCTYPE html>' . x | ex file
Wildcard
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.