Có cách nào để ngăn sed diễn giải chuỗi thay thế không? [đóng cửa]


14

Nếu bạn muốn thay thế một từ khóa bằng một chuỗi bằng sed, sed sẽ cố gắng diễn giải chuỗi thay thế của bạn. Nếu chuỗi thay thế xảy ra có các ký tự mà sed coi là đặc biệt, như ký tự '/', nó sẽ thất bại, trừ khi tất nhiên bạn có nghĩa là chuỗi thay thế của bạn có các ký tự cho sed biết cách hành động.

Ví dụ:

VAR="hi/"

sed "s/KEYWORD/$VAR/g" somefile

Có cách nào để bảo sed đừng cố diễn giải chuỗi thay thế cho các ký tự đặc biệt không? Tất cả những gì tôi muốn là có thể thay thế một từ khóa trong một tệp bằng nội dung của một biến, bất kể nội dung đó là gì.


Nếu bạn muốn đặt các ký tự đặc biệt vào sedvà khiến chúng không phải là đặc biệt, chỉ cần gạch chéo lại thoát khỏi chúng. VAR='hi\/'không có vấn đề như vậy.
tự đại diện

6
Tại sao tất cả các downvote? Có vẻ như một câu hỏi hoàn toàn hợp lý với tôi
roaima

sed(1)chỉ diễn giải những gì nó nhận được. Trong trường hợp của bạn, nó nhận được điều đó thông qua phép nội suy shell. Tôi tin rằng bạn không thể làm như bạn muốn, nhưng hãy kiểm tra hướng dẫn. Tôi biết trong Perl (tạo ra một sedsự thay thế có thể vượt qua , với các biểu thức chính quy phong phú hơn nhiều) bạn có thể chỉ định một chuỗi sẽ được thực hiện theo nghĩa đen, một lần nữa, kiểm tra hướng dẫn.
vonbrand

Câu trả lời:


4

Chỉ có 4 ký tự đặc biệt ở phần thay thế: \, &, xuống dòng và dấu phân cách ( ref )

$ VAR='abc/def&ghi\foo
next line'

$ repl=$(sed -e 's/[&\\/]/\\&/g; s/$/\\/' -e '$s/\\$//' <<<"$VAR")

$ echo "$repl"
abc\/def\&ghi\\foo\
next line

$ echo ZYX | sed "s/Y/$repl/g"
Zabc/def&ghi\foo
next lineX

Điều này có cùng một vấn đề như giải pháp của Antti - nếu chuỗi thay thế vượt quá một độ dài nhất định, bạn sẽ gặp lỗi "Danh sách đối số quá dài". Ngoài ra, nếu chuỗi thay thế có '[', ']', '*', '.' Và các ký tự khác như vậy thì sao? Sed thực sự sẽ không giải thích những người?
Tal

Phía thay thế s///không một biểu hiện thường xuyên, nó thực sự chỉ là một chuỗi (trừ xuyệc ngược-thoát và &). Nếu chuỗi thay thế quá dài, lớp lót một lớp vỏ không phải là giải pháp của bạn.
glenn jackman

Một danh sách rất hữu ích nếu, ví dụ, chuỗi thay thế của bạn là văn bản được mã hóa base64 (ví dụ: thay thế một trình giữ chỗ bằng khóa SHA256). Sau đó, nó chỉ là dấu phân cách để lo lắng.
Heath Raftery

4

Bạn có thể sử dụng Perl thay vì sed với -p(vòng lặp giả định trên đầu vào) và -e(đưa ra chương trình trên dòng lệnh). Với Perl, bạn có thể truy cập các biến môi trường mà không cần nội suy các biến này trong shell. Lưu ý rằng biến cần phải được xuất :

export VAR='hi/'
perl -p -e 's/KEYWORD/$ENV{VAR}/g' somefile

Nếu bạn không muốn xuất biến ở khắp mọi nơi, thì chỉ cung cấp biến đó cho quy trình đó:

PATTERN="$VAR" perl -p -e 's/KEYWORD/$ENV{PATTERN}/g' somefile

Xin lưu ý rằng cú pháp biểu thức chính quy của Perl theo mặc định hơi khác so với sed.


Điều này có vẻ rất hứa hẹn, nhưng khi kiểm tra nó, tôi gặp lỗi "Danh sách đối số quá dài" vì chuỗi thay thế của tôi quá dài, điều này có ý nghĩa - sử dụng phương pháp này, chúng tôi đang sử dụng toàn bộ chuỗi thay thế như một phần của các đối số chúng tôi đưa ra để perl, vì vậy có một giới hạn về thời gian có thể.
Tal

1
Không, nó sẽ đi trong PATTERN biến môi trường , không phải đối số. Trong mọi trường hợp, lỗi này sẽ xảy ra E2BIG, nếu bạn sử dụng sed.
Antti Haapala

2

Giải pháp rất đơn giản mà vẫn xử lý chính xác phần lớn các giá trị biến, sẽ là sử dụng một ký tự không in làm dấu phân cách cho sedlệnh thay thế.

Trong vibạn có thể thoát bất kỳ ký tự điều khiển nào bằng cách gõ Ctrl-V (thường được viết là ^V). Vì vậy, nếu bạn sử dụng một số ký tự điều khiển (tôi thường sử dụng ^Anhư một dấu phân cách trong các trường hợp này) thì sedlệnh của bạn sẽ chỉ bị hỏng nếu ký tự không in đó xuất hiện trong biến bạn đang thả vào.

Vì vậy, bạn sẽ gõ "s^V^AKEYWORD^V^A$VAR^V^Ag"và những gì bạn sẽ nhận được (trong vi) sẽ như thế nào:

sed "s^AKEYWORD^A$VAR^Ag" somefile

Điều này sẽ hoạt động miễn là $VARkhông chứa ký tự không in ấn ^Amà cực kỳ khó xảy ra.


Tất nhiên, nếu bạn chuyển đầu vào của người dùng vào giá trị của $VARthì tất cả các cược sẽ tắt và bạn nên vệ sinh kỹ lưỡng đầu vào của mình hơn là dựa vào các ký tự điều khiển khó nhập cho người dùng trung bình.


Tuy nhiên, thực sự có nhiều thứ phải cẩn thận hơn chuỗi phân cách. Chẳng hạn, &khi có mặt trong một chuỗi thay thế, có nghĩa là "toàn bộ văn bản đã được khớp." Ví dụ: s/stu../my&/sẽ thay thế "Stuff" bằng "mystuff", "stung" bằng "mystung", v.v. Vì vậy, nếu bạn có thể có bất kỳ ký tự nào trong biến mà bạn thả vào như một chuỗi thay thế, nhưng bạn muốn sử dụng nghĩa đen chỉ giá trị của biến, sau đó bạn có một số dữ liệu vệ sinh để làm trước khi bạn có thể sử dụng biến làm chuỗi thay thế sed. (Tuy nhiên, việc vệ sinh dữ liệu cũng có thể được thực hiện sed.)


Đó là quan điểm của tôi - thay thế một chuỗi bằng một chuỗi khác là một thao tác rất đơn giản. Có thực sự cần phải phức tạp như tìm ra nhân vật nào mà sed không thích, và sử dụng sed để vệ sinh đầu vào của chính mình không? Điều đó nghe có vẻ vô lý và hỗn độn không cần thiết. Tôi không phải là một lập trình viên chuyên nghiệp, nhưng tôi khá chắc chắn rằng tôi có thể mã hóa một hàm nhỏ thay thế từ khóa bằng một chuỗi bằng bất kỳ ngôn ngữ nào tôi từng gặp, bao gồm cả bash - Tôi chỉ hy vọng vào một Linux đơn giản giải pháp sử dụng các công cụ hiện có - tôi không thể tin rằng không có giải pháp nào ngoài đó.
Tal

1
@Tal, nếu chuỗi thay thế của bạn dài "100 trang" như bạn đề cập trong một nhận xét khác ... bạn khó có thể gọi đó là trường hợp sử dụng "đơn giản". Câu trả lời ở đây là Perl, nhân tiện, tôi không học được Perl. Sự phức tạp ở đây xuất phát từ thực tế là bạn muốn cho phép bất kỳ đầu vào tùy ý như là một chuỗi thay thế trong một regex .
tự đại diện

Có rất nhiều giải pháp khác bạn có thể sử dụng, nhiều giải pháp rất đơn giản. Ví dụ, nếu chuỗi thay thế của bạn thực sự là dòng dựa và không cần phải được chèn vào giữa của một dòng, sử dụng sedcủa ilệnh nsert. Nhưng sedkhông phải là một công cụ tốt để xử lý số lượng lớn văn bản theo những cách phức tạp. Tôi sẽ đăng một câu trả lời khác cho thấy làm thế nào để làm điều này với awk.
tự đại diện

1

Bạn có thể sử dụng một ,hoặc |thay thế và nó sẽ lấy nó như một người tách biệt và về mặt kỹ thuật bạn có thể sử dụng bất cứ điều gì

từ trang người đàn ông

\cregexpc
           Match lines matching the regular expression regexp.  The  c  may
      be any character.

Như bạn có thể thấy, bạn nên bắt đầu bằng \ trước dấu phân cách của bạn ở đầu, sau đó bạn có thể sử dụng nó làm dấu phân cách.

từ tài liệu http://www.gnu.org/software/sed/manual/sed.html#The-_0022s_0022-Command :

The / characters may be uniformly replaced by any other single character 
within any given s command.

The / character (or whatever other character is used in its stead) can appear in 
the regexp or replacement only if it is preceded by a \ character.

Thí dụ:

sed -e 'somevar|s|foo|bar|'
echo "Hello all" | sed "s_all_user_"
echo "Hello all" | sed "s,all,user,"

echo "Hello/ World" | sed "s,Hello/,Neo,"


Bạn đang nói về việc cho phép sử dụng một ký tự cụ thể, duy nhất trong chuỗi thay thế - trong trường hợp này là "/". Tôi đang nói về việc ngăn chặn nó cố gắng diễn giải chuỗi thay thế hoàn toàn. Bất kể bạn sử dụng ký tự nào ("/", ",", "|", v.v.), bạn luôn có nguy cơ khiến nhân vật đó bật lên trong chuỗi thay thế. Ngoài ra, nhân vật ban đầu không phải là nhân vật đặc biệt duy nhất mà sed quan tâm, phải không?
Tal

@Tal không nó có thể lấy bất cứ thứ gì thay thế /và nó sẽ bỏ qua một /cách vui vẻ như tôi đã chỉ ra .. trên thực tế, bạn thậm chí có thể tìm kiếm nó và thay thế nó trong một chuỗi >>> tôi đã chỉnh sửa bằng một ví dụ >>> công cụ không an toàn và bạn sẽ luôn tìm thấy một anh chàng thông minh hơn
dùng3566929

@Tal tại sao bạn muốn ngăn nó diễn giải? ý tôi là đó là việc sử dụng sedở nơi đầu tiên, dự án của bạn là gì?
dùng3566929

Tất cả tôi cần là thay thế một từ khóa bằng một chuỗi. sed dường như là cách phổ biến nhất, cho đến nay, để làm điều này trong linux. Chuỗi có thể dài 100 trang. Tôi không muốn thử vệ sinh chuỗi để sed không bị bối rối khi đọc nó - Tôi muốn nó có thể xử lý bất kỳ ký tự nào trong chuỗi, và bằng cách "xử lý", ý tôi là không cố gắng tìm phép thuật ý nghĩa bên trong.
Tal

1
@Tal, bashKHÔNG cho thao tác chuỗi. Ở tất cả, ở tất cả, ở tất cả. Nó là để thao tác tập tinphối hợp lệnh . Nó có một số chức năng tiện dụng được tích hợp sẵn cho các chuỗi, nhưng thực sự hạn chế và không nhanh lắm nếu đó là việc chính bạn đang làm. Xem "Tại sao sử dụng vòng lặp shell để xử lý văn bản được coi là thực tiễn xấu?" Một số công cụ mà được thiết kế để xử lý văn bản là, theo thứ tự từ cơ bản nhất đối với hầu hết mạnh mẽ: sed, awkvà Perl.
tự đại diện

1

Nếu nó dựa trên dòng và chỉ có một dòng để thay thế, tôi khuyên bạn nên chuẩn bị trước tệp bằng dòng thay thế bằng cách sử dụng printf, lưu trữ dòng đầu tiên đó trong sedkhông gian giữ và thả nó vào khi cần. Bằng cách này, bạn không phải lo lắng về các ký tự đặc biệt. (Giả định duy nhất ở đây là $VARchứa một dòng văn bản không có bất kỳ dòng mới nào, đó là những gì bạn đã nói trong các nhận xét.) Khác với dòng mới, VAR có thể chứa bất cứ điều gì và điều này sẽ hoạt động bất kể.

VAR=whatever
{ printf '%s\n' "$VAR";cat somefile; } | sed '1{h;d;};/KEYWORD/g'

printf '%s\n'sẽ in các nội dung $VARdưới dạng một chuỗi ký tự, bất kể nội dung của nó, theo sau là một dòng mới. ( echosẽ làm những việc khác trong một số trường hợp, ví dụ nếu nội dung $VARbắt đầu bằng dấu gạch nối, nó sẽ được hiểu là cờ tùy chọn được chuyển đến echo.)

Các dấu ngoặc nhọn được sử dụng để thêm vào đầu ra của printfnội dung somefilekhi nó được truyền vào sed. Không gian riêng biệt ngăn cách các dấu ngoặc nhọn là quan trọng ở đây, cũng như dấu chấm phẩy trước dấu ngoặc nhọn đóng.

1{h;d;};như một sedlệnh sẽ lưu trữ các dòng đầu tiên của văn bản trong sed's không gian giữ , sau đó delete dòng (chứ không phải in nó).

/KEYWORD/áp dụng các hành động sau cho tất cả các dòng có chứa KEYWORD. Hành động là get, lấy nội dung của không gian giữ và thả nó vào vị trí của không gian mẫu. Nói cách khác, toàn bộ dòng hiện tại. (Điều này không chỉ để thay thế một phần của một dòng.) Nhân tiện, không gian giữ không bị xóa hết, chỉ được sao chép vào không gian mẫu, thay thế bất cứ thứ gì ở đó.

Nếu bạn muốn neo regex của mình để nó không khớp với một dòng chỉ chứa KEYWORD mà chỉ là một dòng không có gì khác trên dòng ngoài KEYWORD, hãy thêm một đầu của dòng neo ( ^) và cuối dòng neo ( $) vào regex của bạn:

VAR=whatever
{ printf '%s\n' "$VAR";cat somefile; } | sed '1{h;d;};/^KEYWORD$/g'

Có vẻ tuyệt vời nếu VAR của bạn dài một dòng. Tôi thực sự đã đề cập trong các ý kiến ​​rằng VAR "có thể dài 100 trang" chứ không phải là một dòng. Xin lỗi vì sự nhầm lẫn.
Tal

0

Bạn có thể gạch chéo ngược - thoát các dấu gạch chéo về phía trước trong chuỗi thay thế của bạn, sử dụng mở rộng tham số thay thế mẫu của Bash. Nó hơi lộn xộn vì những nhát chém về phía trước cũng cần phải được trốn thoát cho Bash.

$ var='a/b/c';var="${var//\//\\/}";echo 'this is a test' | sed "s/i/$var/g"

đầu ra

tha/b/cs a/b/cs a test

Bạn có thể đặt mở rộng tham số trực tiếp vào lệnh sed của bạn:

$ var='a/b/c';echo 'this is a test' | sed "s/i/${var//\//\\/}/g"

nhưng tôi nghĩ rằng hình thức đầu tiên là dễ đọc hơn một chút. Và tất nhiên, nếu bạn sẽ sử dụng lại cùng một kiểu thay thế trong nhiều lệnh sed, thì chỉ cần thực hiện chuyển đổi một lần.

Một lựa chọn khác là sử dụng một tập lệnh được viết bằng awk, perl hoặc Python hoặc chương trình C, để thay thế bạn thay vì sử dụng sed.


Đây là một ví dụ đơn giản trong Python hoạt động nếu từ khóa được thay thế là một dòng hoàn chỉnh trong tệp đầu vào (không tính dòng mới). Như bạn có thể thấy, về cơ bản, nó giống như thuật toán Bash của bạn, nhưng nó đọc tệp đầu vào hiệu quả hơn.

import sys

#Get the keyword and replacement texts from the command line
keyword, replacement = sys.argv[1:]
for line in sys.stdin:
    #Strip any trailing whitespace
    line = line.rstrip()
    if line == keyword:
        line = replacement
    print(line)

Đây chỉ là một cách khác để vệ sinh đầu vào, và không phải là một cách tuyệt vời ở đó, vì nó chỉ xử lý một ký tự cụ thể ('/'). Như Wildcard đã chỉ ra, có nhiều điều cần chú ý hơn là chỉ chuỗi phân cách.
Tal

Cuộc gọi công bằng. Ví dụ, nếu văn bản thay thế có chứa bất kỳ chuỗi thoát ngược nào, chúng sẽ được giải thích, điều này có thể không được mong muốn. Một cách xung quanh đó là chuyển đổi các ký tự có vấn đề (hoặc toàn bộ) thành \xcác chuỗi thoát theo kiểu. Hoặc để sử dụng một chương trình có thể xử lý đầu vào tùy ý, như tôi đã đề cập trong đoạn cuối của tôi.
PM 2Ring

@Tal: Tôi sẽ thêm một ví dụ Python đơn giản vào câu trả lời của tôi.
PM 2Ring

Kịch bản python hoạt động rất tốt và dường như thực hiện chính xác chức năng của tôi, chỉ hiệu quả hơn nhiều. Thật không may, nếu tập lệnh chính là bash (như trong trường hợp của tôi), thì tập lệnh này yêu cầu sử dụng tập lệnh python bên ngoài thứ cấp.
Tal

-1

Đây là con đường tôi đã đi:

#Replaces a keyword with a long string
#
#This is normally done with sed, but sed
#tries to interpret the string you are
#replacing the keyword with too hard
#
#stdin - contents to look through
#Arg 1 - keyword to replace
#Arg 2 - what to replace keyword with
replace() {
        KEYWORD="$1"
        REPLACEMENT_STRING="$2"

        while IFS= read -r LINE
        do
                if [[ "$LINE" == "$KEYWORD" ]]
                then
                        printf "%s\n" "$REPLACEMENT_STRING"
                else
                        printf "%s\n" "$LINE"
                fi
        done < /dev/stdin
}

điều này hoạt động rất tốt trong trường hợp của tôi bởi vì từ khóa của tôi nằm trên một dòng tất cả. Nếu từ khóa nằm trong một dòng với văn bản khác, điều này sẽ không hoạt động.

Tôi vẫn thực sự muốn biết liệu có cách nào dễ dàng để làm điều này không liên quan đến mã hóa giải pháp của riêng tôi hay không.


1
Nếu bạn thực sự lo lắng về các ký tự đặc biệt và sự mạnh mẽ, bạn hoàn toàn không nên sử dụng echo. Sử dụng printfthay thế. thực hiện xử lý văn bản trong một vòng lặp shell là một ý tưởng tồi.
tự đại diện

1
Sẽ rất hữu ích nếu bạn đề cập trong câu hỏi rằng từ khóa sẽ luôn là một dòng hoàn chỉnh. FWIW, bash's readkhá chậm. Nó có nghĩa là để xử lý đầu vào tương tác của người dùng, không phải xử lý tệp văn bản. Nó chậm vì nó đọc stdin char bởi char, thực hiện cuộc gọi hệ thống cho mỗi char.
PM 2Ring

@PM 2Ring Câu hỏi của tôi không đề cập đến việc từ khóa nằm trên một dòng riêng vì tôi không muốn một câu trả lời chỉ hoạt động trong một số trường hợp hạn chế như vậy - Tôi muốn một cái gì đó có thể dễ dàng hoạt động bất kể từ khóa ở đâu được. Tôi cũng không bao giờ nói mã của mình hiệu quả - nếu có, tôi sẽ không tìm kiếm một giải pháp thay thế ...
Tal

@Wildcard Trừ khi tôi thiếu một cái gì đó, printf hoàn toàn diễn giải các ký tự đặc biệt và nhiều hơn so với 'echo' mặc định. printf "hi\n"sẽ làm cho printf in một dòng mới trong khi echo "hi\n"in nó như vậy.
Tal

@Tal, "f" trong printfviết tắt của "format" đối số đầu tiên printflà một chỉ định định dạng . Nếu chỉ định đó %s\n, có nghĩa là "chuỗi theo dòng mới", không có gì trong đối số tiếp theo sẽ được giải thích hoặc dịch bởi printf tất cả . (Tất nhiên, trình bao vẫn có thể diễn giải nó, tốt nhất là dán tất cả vào các dấu ngoặc đơn nếu đó là một chuỗi bằng chữ hoặc dấu ngoặc kép nếu bạn muốn mở rộng biến.) Xem câu trả lời của tôi bằng cách sử dụngprintf để biết thêm chi tiết.
tự đại diện
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.