Sed'in değiştirme dizesini yorumlamasını önlemenin bir yolu var mı? [kapalı]


17

Bir anahtar kelimeyi sed kullanarak bir dize ile değiştirmek istiyorsanız sed, değiştirme dizenizi yorumlamak için çok uğraşır. Yedek dize, '/' karakteri gibi sed'in özel olduğunu düşündüğü karakterlere sahipse, elbette, yedek dizenizin sed'in nasıl hareket edeceğini söyleyen karakterlere sahip olmasını istemediğiniz sürece başarısız olur.

Ör:

VAR="hi/"

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

Sed'e özel karakterler için değiştirme dizesini yorumlamamaya çalışmanın herhangi bir yolu var mı? Tek istediğim, bir dosyadaki bir anahtar kelimeyi, içeriğin ne olduğuna bakılmaksızın bir değişkenin içeriğiyle değiştirebilmektir.


Eğer özel karakterler koymak sedve özel olmamasını istiyorsanız , sadece ters eğik çizgi onları kaçmak. VAR='hi\/'böyle bir sorun çıkarmaz.
Wildcard

6
Neden tüm inişler? Bana çok makul bir soru gibi geliyor
roaima

sed(1)sadece ne aldığını yorumlar. Sizin durumunuzda, bunu bir kabuk enterpolasyonu yoluyla alır. İstediğiniz gibi yapamayacağınıza inanıyorum, ancak kılavuzu kontrol edin. Ben Perl ( sedçok daha zengin düzenli ifadeleri ile fena bir yedek yapar ) biliyorum, bir dize tam anlamıyla alınacak olduğunu belirtebilirsiniz, yine kılavuzu kontrol edin.
vonbrand

Yanıtlar:


5

Sed yerine Perl komutunu kullanabilirsiniz -p(loop over input varsa) ve -e(komut satırında program verin). Perl ile ortam değişkenlerine kabuk içinde enterpolasyon yapmadan erişebilirsiniz . Değişkenin dışa aktarılması gerektiğini unutmayın :

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

Değişkeni her yere dışa aktarmak istemiyorsanız, yalnızca bu işlem için sağlayın:

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

Perl'in normal ifade sözdiziminin varsayılan olarak sed'lerden biraz farklı olduğunu unutmayın.


Bu çok umut verici görünüyordu, ama test ederken, "Değişken listesi çok uzun" hatası alıyorum çünkü değiştirme dizem çok uzun, bu da mantıklı - bu yöntemi kullanarak, değiştirilen argümanların bir parçası olarak tüm değiştirme dizesini kullanıyoruz bu yüzden ne kadar süre olabileceğine dair bir sınır var.
Tal

1
Hayır, argümanlara değil , PATTERN ortam değişkenine girecektir. Her durumda, bu hata E2BIG, kullanırsanız eşit olarak alacağınız olacaktır sed.
Antti Haapala

4

Yedek parçası sadece 4 özel karakter vardır: \, &, yeni satır ve sınırlayıcı ( 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

Bu, Antti'nin çözümü ile aynı soruna sahiptir - değiştirme dizesi belirli bir uzunluğu geçerse, "Bağımsız değişken listesi çok uzun" hatası alırsınız. Ayrıca, değiştirme dizesinde '[', ']', '*', '.' Ve benzeri karakterler varsa ne olur? Sed bunları gerçekten yorumlamaz mı?
Tal

Yerine yan s///olduğu değil gerçekten (ters eğik çizgi-Kaçış için ve dışında sadece zincir, normal bir ifade &). Yedek dize çok uzunsa, bir kabuklu bir astar çözümünüz değildir.
glenn jackman

Örneğin, değiştirme dizeniz base64 kodlu metinse (örneğin, bir yer tutucuyu bir SHA256 anahtarıyla değiştirme) çok yararlı bir liste. O zaman endişelenmeniz gereken sınırlayıcı.
Heath Raftery

2

Değişken değerlerin büyük çoğunluğunu hala doğru şekilde ele alacak en basit çözüm, sedyerine koyma komutunun ayırıcısı olarak yazdırılmamış bir karakter kullanmak olacaktır .

Gelen viEğer yazarak Ctrl-V ile herhangi bir kontrol karakteri kaçabilir (daha yaygın olarak yazılır ^V). Bu nedenle, bazı kontrol karakterleri kullanırsanız ( ^Abu durumlarda genellikle ayırıcı olarak kullanırım ), sedkomutunuz yalnızca bıraktığınız değişkende yazdırılmayan karakter varsa kırılır.

Böylece yazardınız "s^V^AKEYWORD^V^A$VAR^V^Ag"ve ne alacağınız vişöyle görünecektir:

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

Bu , son derece olası $VARolmayan, yazdırılmayan karakteri içermediği sürece çalışır ^A.


Tabii ki, kullanıcı girişini değerine geçiriyorsanız $VAR, tüm bahisler kapalıdır ve ortalama kullanıcı için yazılması zor olan kontrol karakterlerine güvenmek yerine girişinizi iyice sterilize edersiniz.


Aslında dikkat edilmesi gereken sınırlayıcı dizeden daha fazlası var. Örneğin, &bir değiştirme dizesinde bulunduğunda "eşleşen metnin tamamı" anlamına gelir. Örneğin, s/stu../my&/"şeyler" yerine "mystuff", "stung" yerine "mystung", vb. Yerleştirilir . Değişkende yeni bir dize olarak bıraktığınız herhangi bir karakter varsa, ancak yalnızca değişkenin değerini alırsanız, değişkeni bir yedek dize olarak kullanabilmeniz için yapmanız gereken bazı verileri dezenfekte edebilirsiniz sed. (Verilerin dezenfeksiyonu ile sedde yapılabilir .)


Bu benim açımdan - bir dizeyi başka bir dizeyle değiştirmek çok basit bir işlem. Sed'in hangi karakterleri sevmeyeceğini bulmak ve kendi girdisini sterilize etmek için sed'i kullanmak kadar karmaşık mı olmalı? Bu gülünç ve gereksiz yere kıvrık geliyor. Ben profesyonel bir programcı değilim, ama bash dahil olmak üzere şimdiye kadar karşılaştığım herhangi bir dilde bir dize ile bir anahtar kelimeyi değiştiren küçük bir işlevi kodlayabileceğime eminim - sadece basit bir Linux umuyordum mevcut araçları kullanarak çözüm - orada bir tane olmadığına inanamıyorum.
Tal

1
@Tal, başka bir yorumda belirttiğiniz gibi değiştirme dizeniz "100s sayfa uzunluğunda" ise, buna "basit" bir kullanım durumu diyemezsiniz. Buradaki cevap Perl, bu arada — Perl'i henüz öğrenmedim. Karmaşıklık burada bir şekilde HERHANGİ keyfi girişini izin vermek istediğiniz olmasından kaynaklanır yedek dize bir de regex .
Joker

Birçoğu çok basit, kullanabileceğiniz çok sayıda başka çözüm var. Örneğin, değiştirme dizeniz aslında çizgi tabanıysa ve bir satırın ortasına eklenmesi gerekmiyorsa , sed' isnsert komutunu kullanın. Ancak sedçok miktarda metni karmaşık şekillerde işlemek için iyi bir araç değildir. Bunu nasıl yapacağınızı gösteren başka bir cevap göndereceğim awk.
Joker

1

Bunun yerine bir ,veya bir kullanabilirsiniz |ve ayırıcı olarak alacaktır ve teknik olarak her şeyi kullanabilirsiniz

adam sayfasından

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

Gördüğünüz gibi, başında ayırıcıdan önce bir \ ile başlamalısınız, o zaman ayırıcı olarak kullanabilirsiniz.

http://www.gnu.org/software/sed/manual/sed.html#The-_0022s_0022-Command belgelerinden :

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.

Misal:

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


Değiştirme dizesinde tek bir belirli karakterin kullanılmasına izin vermenizden bahsediyorsunuz - bu durumda "/". Yedek dize tamamen yorumlamaya çalışmasını önlemek hakkında konuşuyorum. Hangi karakteri kullanırsanız kullanın ("/", ",", "|", vb.) Her zaman bu karakterin yedek dizede görünme riskiyle karşı karşıya kalırsınız. Ayrıca, ilk karakter sed'in önem verdiği tek özel karakter değil, değil mi?
Tal

@Tal hayır bunun yerine bir şey alabilir /ve /ben sadece işaret ettiğim gibi mutlu bir şekilde görmezden gelecektir .. aslında, hatta onu aramak ve bir dizede değiştirebilirsiniz >>> bir örnekle düzenledim >>> bunlar şeyler o kadar güvenli değil ve her zaman daha akıllı bir ahbap bulacaksınız
user3566929

@Tal neden yorumlanmasını engellemek istiyorsun? yani sedilk etapta kullanımı bu, projeniz nedir?
user3566929

Tek ihtiyacım olan bir anahtar kelime bir dize ile değiştirmek. sed, linux'da bunu yapmanın en yaygın yolu gibi görünüyor. Dize 100 sayfa uzunluğunda olabilir. Ben dize sanitize denemek istemiyorum böylece sed okurken korkmaz - Ben dize herhangi bir karakter işlemek için istiyorum, ve "kolu", ben sihirli bulmaya çalışmayın demek içindeki anlam.
Tal

1
@Tal, basholduğu DEĞİL dize manipülasyon için. Hiç, hiç, hiç. Bu içindir dosya manipülasyon ve komut koordinasyonu . Dizeler için bazı kullanışlı işlevselliklere sahip olur , ancak yaptığınız ana şey bu ise gerçekten sınırlı ve çok hızlı değildir. Bkz. "Neden kötü uygulama olarak kabul edilen metni işlemek için bir kabuk döngüsü kullanılıyor?" Bazı araçlar vardır en güçlü en temel alınan sırayla, olan metin işleme için tasarlanmış: sed, awkve Perl.
Wildcard

1

Satır tabanlı ve değiştirilecek yalnızca bir satır varsa, dosyanın kendisini kullanarak yeni satırla önermenizi, printfilk satırı sedbekletme alanında saklamanızı ve gerektiği gibi bırakmanızı öneririm . Bu şekilde özel karakterler için endişelenmenize gerek kalmaz. (Buradaki tek varsayım, $VARherhangi bir yeni satır içermeyen tek bir metin satırı içermesidir, bu da zaten yorumlarda söylediğiniz şeydir.) Yeni satırlar dışında, VAR herhangi bir şey içerebilir ve bu ne olursa olsun işe yarayacaktır.

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

printf '%s\n'içeriğini, içeriğinden $VARbağımsız olarak değişmez bir dize, ardından bir satırsonu olarak yazdırır . ( echoörneğin, içeriğinin $VARbir tire işareti ile başlaması gibi bazı durumlarda başka şeyler yapar; bu, geçirilen bir seçenek bayrağı olarak yorumlanır echo.)

Parantezler, iletildiği sırada printfiçeriğinin çıktısının önüne geçmek somefileiçin kullanılır sed. Kıvırcık parantezleri kendiliğinden ayıran boşluk, tıpkı kapanış küme ayracı öncesi noktalı virgül gibi önemlidir.

1{h;d;};Bir şekilde sedkomut metnin ilk satırını saklayacak sedbireyin tutma alanı , daha sonra d(daha doğrusu yazdırmadan yerine) hattını elete.

/KEYWORD/aşağıdaki işlemleri içeren tüm satırlara uygular KEYWORD. Eylem, gtutma boşluğunun içeriğini alan ve onu desen boşluğunun yerine - yani mevcut satırın tamamına - bırakan et'tir. (Bu, bir satırın yalnızca bir kısmını değiştirmek için değildir .) Bekletme alanı boşaltılmaz, bu arada, sadece desen alanına kopyalanır ve orada ne varsa değiştirilir.

İsterseniz demirlemek bu sadece bir satırla aynı etmeyecek şekilde regex içeren KELİMEYİ ancak çizgi ama KEYWORD ile ilgili başka bir şey yok, sadece bir çizgi, hat çapa (bir başlangıcı eklemek ^) ve çizgi çapa sonuna ( $) için normal ifadeniz:

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

VAR'ınız bir satır uzunluğundaysa harika görünüyor. Aslında VAR'ın bir satır yerine "100 sayfa uzunluğunda olabileceğini" söylemiştim. Karışıklık için özür dilerim.
Tal

0

Bash'in desen değiştirme parametresi genişletmesini kullanarak değiştirme dizenizdeki eğik çizgileri ters eğik çizgiden-kaçabilirsiniz. Biraz dağınık çünkü ön eğik çizgilerin de Bash için kaçması gerekiyor.

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

çıktı

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

Sen olabilir senin sed komutu doğrudan parametre genişleme koydu:

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

ama bence ilk form biraz daha okunabilir. Ve elbette, aynı değiştirme desenini birden fazla sed komutunda yeniden kullanacaksanız, dönüşümü bir kez yapmak mantıklıdır.

Başka bir seçenek, sed yerine yerine ikamelarınızı yapmak için awk, perl veya Python ile yazılmış bir komut dosyası veya bir C programı kullanmak olabilir.


Python'da, değiştirilecek anahtar kelime giriş dosyasında eksiksiz bir satırsa (satırsonu sayılmıyorsa) çalışan basit bir örnek. Gördüğünüz gibi, aslında Bash örneğinizle aynı algoritma, ancak giriş dosyasını daha verimli bir şekilde okuyor.

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)

Bu, girdiyi sterilize etmenin başka bir yoludur ve sadece belirli bir karakteri ('/') ele aldığından, büyük bir giriş değildir. Wildcard'ın işaret ettiği gibi, dikkat edilmesi gereken sadece sınırlayıcı dizeden daha fazlasıdır.
Tal

Adil çağrı. Örneğin, değiştirilen metin ters eğik çizgiden kaçan diziler içeriyorsa, bunlar istenmeyebilir. Bunun bir yolu, sorunlu karakterleri (veya her şeyi) \xtarzı kaçış dizilerine dönüştürmek olacaktır. Ya da son paragrafımda bahsettiğim gibi gelişigüzel girdileri işleyebilen bir program kullanmak.
PM 2Ring

@Tal: Cevabıma basit bir Python örneği ekleyeceğim.
PM 2Ring

Python betiği harika çalışıyor ve işlevimin ne yaptığını tam olarak yapıyor gibi görünüyor, sadece çok daha verimli. Ne yazık ki, ana komut dosyası bash ise (benim durumumda olduğu gibi), bu ikincil bir harici python komut dosyasının kullanılmasını gerektirir.
Tal

-1

Bu şekilde gittim:

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

benim durumumda bu harika çalışıyor çünkü anahtar kelimem tek başına bir satırda. Anahtar kelime başka bir metinle aynı çizgideyse, bu işe yaramaz.

Kendi çözümümü kodlamayı gerektirmeyen bunu yapmanın kolay bir yolu olup olmadığını hala bilmek istiyorum.


1
Özel karakterler ve sağlamlık konusunda gerçekten endişeleniyorsanız, hiç kullanmamalısınız echo. Bunun yerine kullanın printf. Ve bir kabuk döngüsünde metin işleme yapıyor kötü bir fikirdir.
Wildcard

1
Sorunda anahtar kelimenin her zaman tam bir satır olacağını belirtmeniz yararlı olacaktır. FWIW, bash readoldukça yavaş. Metin dosyası işleme için değil, etkileşimli kullanıcı girişini işlemek içindir. Yavaş çünkü char tarafından stdin karakterini okuyor, her karakter için bir sistem çağrısı yapıyor.
PM 2Ring

@PM 2Ring Sorum, anahtar kelimenin kendi satırında olduğundan bahsetmedi çünkü sadece sınırlı sayıda durumda çalışan bir cevap istemiyorum - anahtar kelimenin nerede olursa olsun kolayca çalışabilecek bir şey istedim oldu. Kodumun etkili olduğunu da söylemedim - olsaydı, alternatif
Tal

@Wildcard Bir şey kaçırmadıkça, printf kesinlikle özel karakterleri ve varsayılan 'echo' dan çok daha fazlasını yorumlar. olduğu gibi yazdırırken printf "hi\n"printf yeni satır echo "hi\n"yazdırır.
Tal

@Tal, içindeki "f" printf"format" anlamına gelir; ilk argüman printfbir format belirleyicidir. Bu belirteci ise %s\n, anlamına gelen "dizesi yeni satır ardından", hiçbir şey bir sonraki argüman yorumlanır veya tarafından tercüme edilecek printf hiç . (Kabuk yine de yorumlayabilir; elbette, değişmez bir dize ise hepsini tek tırnaklara veya değişken genişletme istiyorsanız çift tırnaklara yapıştırın.) Daha fazla ayrıntı için cevabımıprintf görün .
Wildcard
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.