Apakah ada cara untuk mencegah sed dari menafsirkan string pengganti? [Tutup]


16

Jika Anda ingin mengganti kata kunci dengan string menggunakan sed, sed berusaha keras untuk mengartikan string pengganti Anda. Jika string pengganti kebetulan memiliki karakter yang dianggap spesial, seperti karakter '/', itu akan gagal, kecuali tentu saja Anda bermaksud string pengganti Anda memiliki karakter yang memberi tahu bagaimana bertindak.

Ex:

VAR="hi/"

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

Apakah ada cara untuk memberitahu sed untuk tidak mencoba menafsirkan string pengganti untuk karakter khusus? Yang saya inginkan adalah dapat mengganti kata kunci dalam file dengan konten variabel, apa pun kontennya.


Jika Anda ingin memasukkan karakter khusus seddan membuatnya tidak istimewa, cukup melarikan diri backslash mereka. VAR='hi\/'tidak memberikan masalah seperti itu.
Wildcard

6
Mengapa semua downvotes? Tampaknya pertanyaan yang sangat masuk akal bagi saya
roaima

sed(1)hanya menafsirkan apa yang didapatnya. Dalam kasus Anda, ia mendapatkannya melalui interpolasi shell. Saya percaya Anda tidak dapat melakukan apa yang Anda inginkan, tetapi periksa manualnya. Saya tahu di Perl (yang membuat sedpenggantian lumayan , dengan ekspresi reguler lebih kaya) Anda dapat menentukan string yang harus diambil secara harfiah, sekali lagi, periksa manual.
vonbrand

Jawaban:


4

Hanya ada 4 karakter khusus di bagian pengganti: \, &, baris baru dan pembatas ( 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

Ini memiliki masalah yang sama dengan solusi Antti - jika string pengganti melewati panjang tertentu, Anda mendapatkan kesalahan "Daftar argumen terlalu panjang". Juga, bagaimana jika string pengganti memiliki '[', ']', '*', '.', Dan karakter lain seperti itu? Akankah sed benar tidak menafsirkan itu?
Tal

Sisi penggantian s///adalah tidak ekspresi reguler, itu benar-benar hanya string (kecuali untuk backslash-lolos dan &). Jika string pengganti terlalu panjang, shell satu-liner bukan solusi Anda.
glenn jackman

Daftar yang sangat berguna jika, misalnya, string pengganti Anda adalah teks yang disandikan base64 (mis., Mengganti placeholder dengan kunci SHA256). Maka hanya pembatas yang perlu dikhawatirkan.
Heath Raftery

4

Anda dapat menggunakan Perl bukannya sed dengan -p(menganggap loop over input) dan -e(berikan program pada baris perintah). Dengan Perl, Anda dapat mengakses variabel lingkungan tanpa menyisipkan ini di shell. Perhatikan bahwa variabel perlu diekspor :

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

Jika Anda tidak ingin mengekspor variabel di mana-mana, maka berikan saja untuk proses itu saja:

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

Perhatikan, bahwa sintaks ekspresi reguler Perl secara default sedikit berbeda dari sed.


Ini sepertinya sangat menjanjikan, tetapi ketika mengujinya, saya mendapatkan kesalahan "Daftar argumen terlalu panjang" karena string pengganti saya terlalu panjang, yang masuk akal - menggunakan metode ini, kami menggunakan seluruh string pengganti sebagai bagian dari argumen yang kami berikan untuk perl, jadi ada batasan berapa lama bisa.
Tal

1
Tidak, itu akan masuk dalam PATTERN variabel lingkungan , bukan argumen. Bagaimanapun, kesalahan ini akan menjadi E2BIG, yang Anda akan dapatkan jika Anda digunakan sed.
Antti Haapala

2

Solusi yang paling sederhana yang masih akan menangani sebagian besar nilai variabel dengan benar, adalah dengan menggunakan karakter non-cetak sebagai pembatas untuk sedperintah pengganti.

Di dalamnya viAnda dapat menghindari karakter kontrol apa pun dengan mengetikkan Ctrl-V (lebih umum ditulis sebagai ^V). Jadi, jika Anda menggunakan beberapa karakter kontrol (saya sering menggunakan ^Asebagai pembatas dalam kasus ini) maka sedperintah Anda hanya akan pecah jika karakter yang tidak tercetak itu ada dalam variabel yang Anda masukkan .

Jadi, Anda mengetik "s^V^AKEYWORD^V^A$VAR^V^Ag"dan apa yang akan Anda dapatkan vi:

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

Ini akan berfungsi selama $VARtidak mengandung karakter non-cetak ^A— yang sangat tidak mungkin.


Tentu saja, jika Anda memasukkan input pengguna ke nilai $VAR, maka semua taruhan dimatikan dan Anda sebaiknya membersihkan input Anda secara menyeluruh daripada mengandalkan karakter kontrol yang sulit diketik untuk rata-rata pengguna.


Sebenarnya ada lebih banyak yang harus diperhatikan daripada string pembatas. Misalnya, &ketika ada dalam string pengganti, berarti "seluruh teks yang cocok." Misalnya, s/stu../my&/akan menggantikan "barang" dengan "mystuff", "tersengat" dengan "mystung", dll Jadi jika Anda mungkin memiliki setiap karakter dalam variabel yang Anda menjatuhkan berada di sebagai string pengganti, tetapi Anda ingin menggunakan literal yang nilai variabel saja, maka Anda memiliki beberapa sanitasi data yang harus dilakukan sebelum Anda dapat menggunakan variabel sebagai string pengganti sed. (Namun, sanitasi data dapat dilakukan dengan sedjuga.)


Itulah maksud saya - mengganti string dengan string lain adalah operasi yang sangat sederhana. Apakah itu benar-benar harus rumit seperti mencari tahu karakter mana yang tidak akan disukai, dan menggunakan sed untuk membersihkan inputnya sendiri? Kedengarannya berbelit-belit dan tidak perlu. Saya bukan seorang programmer profesional, tapi saya cukup yakin saya dapat membuat kode fungsi kecil yang menggantikan kata kunci dengan string dalam hampir semua bahasa yang pernah saya temui, termasuk bash - Saya hanya berharap untuk Linux yang sederhana solusi menggunakan alat yang ada - Saya tidak percaya tidak ada satu di luar sana.
Tal

1
@Tal, jika string pengganti Anda "sepanjang 100 halaman" seperti yang Anda sebutkan di komentar lain ... Anda hampir tidak dapat menyebutnya sebagai case use "sederhana". Omong-omong, jawabannya adalah Perl — saya belum pernah belajar Perl. Kompleksitas di sini berasal dari kenyataan bahwa Anda ingin mengizinkan input APAPUN sewenang-wenang sebagai string pengganti dalam suatu regex .
Wildcard

Ada banyak solusi lain yang bisa Anda gunakan, banyak di antaranya sangat sederhana. Misalnya, jika string pengganti Anda sebenarnya garis berdasarkan dan tidak perlu dimasukkan dalam tengah dari garis, penggunaan sed's iperintah nsert. Tetapi sedbukan alat yang baik untuk memproses sejumlah besar teks dengan cara yang kompleks. Saya akan mengirim jawaban lain yang menunjukkan bagaimana melakukan ini awk.
Wildcard

1

Anda bisa menggunakan a ,atau |sebaliknya dan itu akan menganggapnya sebagai pemisah dan secara teknis Anda bisa menggunakan apa saja

dari halaman manual

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

Seperti yang Anda lihat, Anda harus mulai dengan \ sebelum pemisah Anda di awal, kemudian Anda dapat menggunakannya sebagai pemisah.

dari dokumentasi 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.

Contoh:

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,"


Anda berbicara tentang mengizinkan penggunaan satu karakter khusus dalam string pengganti - dalam hal ini, "/". Saya sedang berbicara tentang mencegahnya mencoba menafsirkan string pengganti sama sekali. Tidak peduli karakter apa yang Anda gunakan ("/", ",", "|", dll.) Anda selalu mengambil risiko karakter tersebut muncul di string pengganti. Juga, karakter awal bukan satu-satunya karakter khusus yang sed peduli, bukan?
Tal

@Tal tidak itu bisa mengambil apa saja alih-alih /dan itu akan mengabaikan /bahagia karena saya baru saja menunjukkan .. pada kenyataannya, Anda bahkan dapat mencarinya dan menggantinya dalam string >>> saya telah diedit dengan contoh >>> ini hal-hal tidak begitu aman dan Anda selalu akan menemukan pria yang lebih pintar
user3566929

@Tal mengapa Anda ingin mencegahnya menafsirkan? maksud saya itu adalah penggunaan seddi tempat pertama, apa proyek Anda?
user3566929

Yang saya butuhkan adalah mengganti kata kunci dengan string. sed tampaknya menjadi cara paling umum, sejauh ini, untuk melakukan ini di linux. Panjang string bisa 100 halaman. Saya tidak ingin mencoba membersihkan string sehingga tidak panik ketika membacanya - saya ingin itu dapat menangani karakter dalam string, dan dengan "menangani", maksud saya tidak mencoba untuk menemukan sihir artinya di dalam.
Tal

1
@Tal, bashadalah TIDAK untuk manipulasi string. Sama sekali, sama sekali, sama sekali. Ini untuk manipulasi file dan koordinasi perintah . Kebetulan memiliki beberapa fungsionalitas berguna untuk string, tetapi sangat terbatas dan tidak terlalu cepat sama sekali jika itu hal utama yang Anda lakukan. Lihat "Mengapa menggunakan shell loop untuk memproses teks yang dianggap praktik buruk?" Beberapa alat yang yang dirancang untuk pengolahan teks, dalam urutan dari yang paling dasar untuk paling kuat: sed, awkdan Perl.
Wildcard

1

Jika berbasis garis dan hanya satu baris untuk diganti, saya sarankan untuk menggunakan file itu sendiri dengan menggunakan baris pengganti printf, menyimpan baris pertama di sedruang penahanan, dan meletakkannya sesuai kebutuhan. Dengan cara ini Anda tidak perlu khawatir tentang karakter khusus sama sekali. (Satu-satunya asumsi di sini adalah yang $VARberisi satu baris teks tanpa baris baru, yang sudah Anda katakan di komentar.) Selain baris baru, VAR dapat berisi apa pun dan ini akan berfungsi apa pun.

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

printf '%s\n'akan mencetak konten $VARsebagai string literal, terlepas dari kontennya, diikuti oleh baris baru. ( echodalam beberapa kasus akan melakukan hal-hal lain, misalnya jika isi $VARdiawali dengan tanda hubung — itu akan ditafsirkan sebagai bendera opsi yang diteruskan echo.)

Kawat gigi digunakan untuk menambahkan output printfke isi somefilesaat dilewatkan sed. Ruang putih yang memisahkan kurung kurawal dengan sendirinya penting di sini, seperti halnya titik koma sebelum kurung kurawal penutupan.

1{h;d;};sebagai sedperintah akan menyimpan baris teks pertama seddi ruang penahanan , lalu dhapus baris (daripada mencetaknya).

/KEYWORD/menerapkan tindakan berikut untuk semua baris yang berisi KEYWORD. Tindakannya adalah get, yang mendapatkan konten dari ruang penahan dan menjatuhkannya sebagai ganti ruang pola — dengan kata lain, seluruh baris saat ini. (Ini bukan untuk mengganti hanya bagian dari garis.) Ruang penahanan tidak dikosongkan, dengan cara, hanya disalin ke ruang pola, menggantikan apa pun yang ada.

Jika Anda ingin melabuhkan regex Anda sehingga tidak akan cocok dengan garis yang hanya berisi KEYWORD tetapi hanya garis di mana tidak ada yang lain di baris itu selain KEYWORD, tambahkan awal jangkar baris ( ^) dan akhir jangkar baris ( $) ke regex Anda:

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

Tampak hebat jika VAR Anda satu baris. Saya sebenarnya menyebutkan dalam komentar bahwa VAR "bisa sepanjang 100 halaman" daripada satu baris. Maaf bila membingungkan.
Tal

0

Anda dapat melakukan backslash-escape dari garis miring di string pengganti Anda, menggunakan ekspansi parameter substitusi pola Bash. Agak berantakan karena garis miring ke depan juga harus diloloskan ke Bash.

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

keluaran

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

Anda bisa menempatkan ekspansi parameter langsung ke perintah sed Anda:

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

tapi saya pikir bentuk pertama sedikit lebih mudah dibaca. Dan tentu saja jika Anda akan menggunakan kembali pola penggantian yang sama dalam beberapa perintah sed, masuk akal untuk hanya melakukan konversi sekali.

Pilihan lain adalah menggunakan skrip yang ditulis dengan awk, perl atau Python, atau program C, untuk melakukan pergantian Anda alih-alih menggunakan sed.


Berikut adalah contoh sederhana dalam Python yang berfungsi jika kata kunci yang akan diganti adalah baris lengkap dalam file input (tidak termasuk baris baru). Seperti yang Anda lihat, ini pada dasarnya algoritma yang sama dengan contoh Bash Anda, tetapi membaca file input lebih efisien.

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)

Ini hanyalah cara lain untuk membersihkan input, dan tidak bagus pada itu, karena hanya menangani satu karakter tertentu ('/'). Seperti yang ditunjukkan Wildcard, ada lebih banyak hal yang harus diperhatikan daripada hanya string pembatas.
Tal

Panggilan yang adil. Misalnya, jika teks pengganti berisi urutan backslash-escaped, mereka akan ditafsirkan, yang mungkin tidak diinginkan. Salah satu cara untuk mengonversi karakter yang bermasalah (atau semuanya) menjadi \xurutan escape -style. Atau menggunakan program yang dapat menangani input sewenang-wenang, seperti yang saya sebutkan di paragraf terakhir saya.
PM 2Ring

@Tal: Saya akan menambahkan contoh Python sederhana untuk jawaban saya.
PM 2Ring

Skrip python berfungsi dengan baik, dan tampaknya melakukan persis apa fungsi saya, hanya jauh lebih efisien. Sayangnya, jika skrip utama adalah bash (seperti dalam kasus saya), ini memerlukan penggunaan skrip python eksternal sekunder.
Tal

-1

Inilah cara saya pergi:

#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
}

ini berfungsi dengan baik dalam kasus saya karena kata kunci saya ada pada satu baris dengan sendirinya. Jika kata kunci sejalan dengan teks lain, ini tidak akan berfungsi.

Saya masih sangat ingin tahu apakah ada cara mudah untuk melakukan ini yang tidak melibatkan pengkodean solusi saya sendiri.


1
Jika Anda benar-benar khawatir tentang karakter khusus dan ketahanan, Anda tidak boleh menggunakan echosama sekali. Gunakan printfsebagai gantinya. Dan melakukan pemrosesan teks dalam shell loop adalah ide yang buruk.
Wildcard

1
Akan sangat membantu jika Anda menyebutkan dalam pertanyaan bahwa kata kunci akan selalu menjadi baris yang lengkap. FWIW, bash readagak lambat. Ini dimaksudkan untuk memproses input pengguna interaktif, bukan pemrosesan file teks. Ini lambat karena membaca stdin char oleh char, membuat panggilan sistem untuk setiap char.
PM 2Ring

@PM 2Ring Pertanyaan saya tidak menyebutkan bahwa kata kunci berada pada barisnya sendiri karena saya tidak ingin jawaban yang hanya berfungsi dalam jumlah kasus yang terbatas - Saya ingin sesuatu yang dapat dengan mudah bekerja di mana pun kata kunci tersebut dulu. Saya juga tidak pernah mengatakan kode saya efisien - jika ya, saya tidak akan mencari alternatif ...
Tal

@Wildcard Kecuali saya kehilangan sesuatu, printf benar-benar mengartikan karakter khusus, dan lebih dari sekadar 'gema' standar. printf "hi\n"akan membuat printf mencetak baris baru saat echo "hi\n"mencetak apa adanya.
Tal

@Tal, "f" dalam printfsingkatan untuk "format" - argumen pertama printfadalah penentu format . Jika specifier itu %s\n, yang berarti "string diikuti oleh baris baru", tidak ada dalam argumen berikutnya yang akan ditafsirkan atau diterjemahkan printf sama sekali . (Shell masih dapat mengartikannya, tentu saja; terbaik tempelkan semuanya dalam tanda kutip tunggal jika itu string literal, atau tanda kutip ganda jika Anda ingin ekspansi variabel.) Lihat jawaban saya menggunakanprintf untuk rincian lebih lanjut.
Wildcard
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.