Değişkendeki karakterleri değiştirmenin en kısa yolu


18

Değişkendeki karakterleri değiştirmenin birçok yolu vardır.

Bulduğum en kısa yol tr:

OUTPUT=a\'b\"c\`d_123and_a_lot_more
OUTPUT=$(echo "$OUTPUT"|tr -d "'\`\"")
echo $OUTPUT

Daha hızlı bir yol var mı? Ve benzeri tırnak için bu alıntı-güvenlidir ', "ve `kendisi?


Bence kullanmaya devam edebilirsin tr. BASH'in PE'si iyidir, ancak bu durumda tr çok daha hızlıdır. örneğin echo "$OUTPUT" | tr -dc '[[:alpha:]]' sadece alfasayısal kullanmak istediğiniz için
Valentin Bajrami

2
Alıntı meraklısı olmakla ilgilendiğiniz için: Değişkenlerinizi daima belirtin! echo "$OUTPUT". Ya da daha iyisi: printf "%s\n" "$OUTPUT". (Ne zaman olur OUTPUT="-n"?)
musiphil

Codegolf'a , özellikle de bash ipuçlarına göz atmayı da düşünebilirsiniz .
hoosierEE

Yanıtlar:


22

Bakalım. Gelebileceğim en kısa zamanda trçözümünüzün bir ayarı :

OUTPUT="$(tr -d "\"\`'" <<<$OUTPUT)"

Diğer alternatifler arasında, şimdiye kadar gösterilenden daha kısa olabilen daha önce bahsedilen değişken ikame yer alır:

OUTPUT="${OUTPUT//[\'\"\`]}"

Ve sedelbette bu karakterler açısından daha uzun olsa da:

OUTPUT="$(sed s/[\'\"\`]//g <<<$OUTPUT)"

En kısa sürede mi, yoksa geçen süre içinde mi demek istediğinizden emin değilim. Uzunluk açısından, bu iki karakter, söz konusu belirli karakterleri kaldırmak söz konusu olduğunda olabildiğince kısa (veya yine de alabilirim). Peki hangisi en hızlı? OUTPUTDeğişkeni örneğinizde olanlara ayarlayarak test ettim ancak birkaç düzine kez tekrarladım:

$ echo ${#OUTPUT} 
4900

$ time tr -d "\"\`'" <<<$OUTPUT
real    0m0.002s
user    0m0.004s
sys     0m0.000s
$ time sed s/[\'\"\`]//g <<<$OUTPUT
real    0m0.005s
user    0m0.000s
sys     0m0.000s
$ time echo ${OUTPUT//[\'\"\`]}
real    0m0.027s
user    0m0.028s
sys     0m0.000s

Gördüğünüz gibi tr, açıkça en hızlı, onu yakından takip ediyor sed. Ayrıca, kullanmak echoaslında kullanmaktan biraz daha hızlı gibi görünüyor <<<:

$ for i in {1..10}; do 
    ( time echo $OUTPUT | tr -d "\"\`'" > /dev/null ) 2>&1
done | grep -oP 'real.*m\K[\d.]+' | awk '{k+=$1;} END{print k/NR}'; 
0.0025
$ for i in {1..10}; do 
    ( time tr -d "\"\`'" <<<$OUTPUT > /dev/null ) 2>&1 
  done | grep -oP 'real.*m\K[\d.]+' | awk '{k+=$1;} END{print k/NR}'; 
0.0029

Fark küçük olduğu için, yukarıdaki testleri her ikisi için 10 kez çalıştırdım ve en hızlısı gerçekten başlamak zorunda olduğun ortaya çıkıyor:

echo $OUTPUT | tr -d "\"\`'" 

Ancak, bir değişkene atama yükünü dikkate aldığınızda, bu tr, kullanımı basit değiştirmeden biraz daha yavaştır:

$ for i in {1..10}; do
    ( time OUTPUT=${OUTPUT//[\'\"\`]} ) 2>&1
  done | grep -oP 'real.*m\K[\d.]+' | awk '{k+=$1;} END{print k/NR}'; 
0.0032

$ for i in {1..10}; do
    ( time OUTPUT=$(echo $OUTPUT | tr -d "\"\`'")) 2>&1
  done | grep -oP 'real.*m\K[\d.]+' | awk '{k+=$1;} END{print k/NR}'; 
0.0044

Sonuç olarak, sadece sonuçları görüntülemek istediğinizde kullanın, trancak bir değişkeni yeniden atamak istiyorsanız, kabuğun dize düzenleme özelliklerini kullanmak, ayrı bir alt kabuk çalıştırma yükünü önlemek için daha hızlıdır.


4
OP, değiştirilen değeri tekrar ayarlamakla ilgilendiğinden, OUTPUTkomut değiştirme alt kabuğu ek yükü trve sedçözümlerini
hesaba katmanız gerekecektir

@ 1_CR evet ama hangi yöntemi kullanırsa kullansaydı bunun önemsiz olduğunu düşündüm.
terdon

1
Pek OUTPUT="${OUTPUT//[`\"\']/}" değil, komut ikamesi
içermiyor

@ 1_CR ah, anlıyorum, evet, oldukça haklısın ve bu da sonucu değiştiriyor. Teşekkürler, cevap düzenlendi.
terdon

2
Komut ikamesi içeren yöntemler, ipin bir miktar yönetilmesinin dezavantajına sahiptir. (Bundan kaçınabilirsiniz, ancak komutu önemli ölçüde daha karmaşık hale getirme pahasına.) Özellikle, komut değiştirme, sondaki satırları kaldırır.
Gilles 'SO- kötü olmayı bırak

15

Değişken ikamesi kullanabilirsiniz :

$ OUTPUT=a\'b\"c\`d
$ echo "$OUTPUT"
a'b"c`d

${parameter//pattern/string}Desenin tüm örneklerini dizeyle değiştirmek için bu sözdizimini kullanın:

$ echo "${OUTPUT//\'/x}"
axb"c`d
$ echo "${OUTPUT//\"/x}"
a'bxc`d
$ echo "${OUTPUT//\`/x}"
a'b"cxd
$ echo "${OUTPUT//[\'\"\`]/x}"
axbxcxd

@ rubo77 echo ${OUTPUT//[`\"\']/x}veriraxbxcxa
kaos

Genişletme "değişken genişletme" olarak adlandırmak yanlıştır. Buna "parametre genişletme" denir.
gena2x

@ gena2x - Yorumunuzun burada ne anlama geldiğini anlamıyorum?
slm

12

Bash veya zsh'de:

OUTPUT="${OUTPUT//[\`\"\']/}"

${VAR//PATTERN/}Desenin tüm örneklerini kaldıracağını unutmayın . Daha fazla bilgi için bash parametresi genişletme

Bu çözüm, harici programlar çalıştırmayı içermediğinden kısa dizeler için en hızlı olmalıdır. Bununla birlikte, çok uzun dizeler için tam tersi geçerlidir - metin işlemleri için özel bir araç kullanmak daha iyidir, örneğin:

$ OUTPUT="$(cat /usr/src/linux/.config)"

$ time (echo $OUTPUT | OUTPUT="${OUTPUT//set/abc}")
real    0m1.766s
user    0m1.681s
sys     0m0.002s

$ time (echo $OUTPUT | sed s/set/abc/g >/dev/null)
real    0m0.094s
user    0m0.078s
sys     0m0.006s

1
Aslında, trdaha hızlı. Regexes ve globlar pahalıdır ve burada harici bir program olmasa da, bash her zaman böyle bir şeyden daha yavaş olacaktır tr.
terdon

Bu büyük ölçüde girdi verilerine ve normal ifade uygulamasına bağlıdır. Cevabınızda bazı büyük veri kümeleri aldınız - ancak veri kümesi küçük olabilir. Veya farklı. Ayrıca, regexp zamanını değil yankı zamanını ölçersiniz, bu yüzden karşılaştırmanızın gerçekten adil olup olmadığından emin olamam.
gena2x

Güzel nokta. Ancak, test yapmadan hız hakkında iddialarda bulunamazsınız. Aslında, bir değişkene atandığında bu daha hızlı görünür, ancak ekrana yazdırırken trkazanır (cevabımı görün). Bunun birçok faktöre bağlı olacağına katılıyorum, ancak tam olarak test etmeden hangisinin kazandığını söyleyemezsiniz.
terdon

6

, Kapalı-şans, sadece yeniden iyon kabuk için tırnak işlemek çalışıyorsanız, o zaman bunu yapabilirsiniz olmadan bunları kaldırmayı, hem bu da ölü basit:

aq() { sh -c 'for a do
       alias "$((i=$i+1))=$a"
       done; alias' -- "$@"
}

Bu işlev kabuğu, elinizdeki herhangi bir arg dizisini tırnak içine alır ve çıktısını yinelenebilir argüman başına artırır.

İşte birkaç argüman ile:

aq \
"here's an
ugly one" \
"this one is \$PATHpretty bad, too" \
'this one```****```; totally sucks'

ÇIKTI

1='here'"'"'s an
ugly one'
2='this one is $PATHpretty bad, too'
3='this one```****```; totally sucks'

Bu çıktı dashgenellikle tek tırnaklı çıktıyı güvenli şekilde tırnak içine alır '"'"'. basholur '\''.

Tek, boşluk olmayan, boş olmayan bir bayt seçimini başka bir tek baytla değiştirmek, $IFSve ile herhangi bir POSIX kabuğunda en hızlı şekilde yapılabilir $*.

set -f; IFS=\"\'\`; set -- $var; printf %s "$*"

ÇIKTI

"some ""crazy """"""""string ""here

Orada sadece printfbunu görebiliyorsunuz, ama tabii ki, eğer yapmışsam:

var="$*"

... printfkomutun $vardeğeri yerine oradaki çıktıda gördüğünüz değer olacaktır.

Ben ne zaman set -fben kabuk talimat değil glob - durumda dize glob desen addedilecek bir karakter içeriyor. Bunu ben çünkü kabukları ayrıştırıcı değişkenler üzerinde alan bölme gerçekleştirdikten sonra glob kalıpları genişletir . globbing gibi yeniden etkinleştirilebilir set +f. Genel olarak - komut dosyalarında - Patlamamı ayarlamak için yararlı buluyorum:

#!/usr/bin/sh -f

Ve daha sonra açıkça globbing etkinleştirmek ile set +fbunu isteyebilirsiniz hat ne olursa olsun.

Alan ayırma işlemi, $IFS .

İki tür $IFSdeğer vardır - $IFSboşluk ve boşluk $IFSolmayan. $IFSboşluk (boşluk, sekme, satırsonu) ayrılmış alanlar tek bir alana sırayla elide olarak belirtilir (veya başka bir şeyden önce yoksa hiçbiri) - yani ...

IFS=\ ; var='      '; printf '<%s>' $var
<>

Ancak diğer tüm oluşumlar için tek bir alanı değerlendirmek üzere belirtilir - kesilmezler.

IFS=/; var='/////'; printf '<%s>' $var
<><><><><>

Tüm değişken genişletmeler varsayılan olarak $IFSsınırlandırılmış veri dizileridir - alanlara göre ayrılırlar $IFS. Bir "alıntı yaptığınızda, o dizi özelliğini geçersiz kılar ve tek bir dize olarak değerlendirirsiniz.

Öyleyse yaptığım zaman ...

IFS=\"\'\`; set -- $var

Kabuğun argüman dizisini 's genişleme $IFStarafından oluşturulan birçok ayrılmış alanlara ayarlıyorum $var. O karakterler için kendisini oluşturan değerleri genişletildiğinde içerdiği $IFSedilir kaybetti - artık sadece saha ayırıcılardır - bunlar \0NUL.

"$*"- diğer çift tırnaklı değişken açılımları gibi, alan ayırma özelliklerini de geçersiz kılar $IFS. Fakat ek olarak , bu ilk bayt yerine $IFS her sınırlandırılmış alan için de "$@". Yani çünkü "oldu ilk değer $IFS sonraki tüm sınırlayıcı haline "de "$*". Ve "gerek yok$IFS bölündüğünüzde de . $IFS Sonra set -- $args başka bir değere tamamen geçebilirsiniz ve yeni ilk baytı alan sınırlayıcıları için görünecektir "$*". Dahası, tüm izlerini tamamen şöyle kaldırabilirsiniz:

set -- $var; IFS=; printf %s "$*"

ÇIKTI

some crazy string here

Çok güzel +1. Gerçekten daha hızlı olup olmadığını merak ediyorum. Cevabımdaki yaklaşımlarla karşılaştırarak zamanlama testleri ekleyebilir misiniz? Seninkinin daha hızlı olacağını umuyorum ama görmek istiyorum.
terdon

@terdon - bu kabuğa bağlıdır. Öyle neredeyse kesinlikle daha hızlı trherhangi kabukta, ama fark içinde şüpheli olduğu bashiçin ${var//$c/$newc/}durum. Bu durumda bile bir miktar marjla daha hızlı olacağını umuyorum, ama genellikle bunun için endişelenmiyorum çünkü bu şeyler için her zaman kullanıyorum dash- bu genellikle her açıdan büyüklük sıralarıyla daha hızlı. Ve bu yüzden karşılaştırmak zor.
mikeserv

@terdon - Denedim. Ama - hatta bash- yapıyor time (IFS=\"\'`; set -- $var; printf %s "$*")ve time (var=${var//\'`/\"/})hem sonuç 0.0000stüm alanlar için sonuçlar. Yanlış bir şey mi yapıyorum? Orada backquote önce bir ters eğik çizgi olması gerekiyordu ama bir yorum kodu alanına nasıl bir backquote koymak bilmiyorum.
mikeserv
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.