วิธีที่สั้นที่สุดในการแทนที่อักขระในตัวแปร


18

มีหลายวิธีในการแทนที่อักขระในตัวแปร

วิธีที่สั้นที่สุดที่ฉันค้นพบคือtr:

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

มีวิธีที่เร็วกว่านี้ไหม และนี่คือข้อความที่ปลอดภัยสำหรับคำพูดเช่น', "และ `ตัวเอง?


ฉันคิดว่าคุณสามารถใช้trต่อไป PE ของ BASH นั้นดี แต่ TR นั้นเร็วกว่ามากในกรณีนี้ เช่นecho "$OUTPUT" | tr -dc '[[:alpha:]]' เนื่องจากคุณต้องการมีตัวอักษรและตัวเลขเท่านั้น
Valentin Bajrami

2
เนื่องจากคุณสนใจที่จะอ้างถึง - เข้าใจ: อ้างตัวแปรของคุณเสมอ! echo "$OUTPUT". หรือดีกว่า: printf "%s\n" "$OUTPUT". (เกิดอะไรขึ้นเมื่อไหร่OUTPUT="-n"?)
musiphil

คุณอาจลองเรียกดูcodegolfโดยเฉพาะเคล็ดลับทุบตี
hoosierEE

คำตอบ:


22

มาดูกัน. สิ่งที่สั้นที่สุดที่ฉันสามารถทำได้คือการปรับแต่งtrโซลูชันของคุณ:

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

ทางเลือกอื่นรวมถึงการแทนที่ตัวแปรที่กล่าวถึงแล้วซึ่งสามารถสั้นกว่าที่แสดงได้จนถึง:

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

และsedแน่นอนว่านี่เป็นตัวละครที่ยาวกว่า:

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

ฉันไม่แน่ใจว่าคุณหมายถึงความยาวสั้นที่สุดหรือในแง่ของเวลา ในแง่ของความยาวทั้งสองนี้สั้นที่สุดเท่าที่จะได้รับ (หรืออย่างที่ฉันสามารถรับได้) เมื่อมันมาถึงการลบตัวละครที่เฉพาะเจาะจงเหล่านั้น ดังนั้นเร็วที่สุด? ฉันทดสอบโดยการตั้งค่าOUTPUTตัวแปรเป็นสิ่งที่คุณมีในตัวอย่างของคุณ แต่ทำซ้ำหลายสิบครั้ง:

$ 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

ในขณะที่คุณสามารถมองเห็นได้อย่างชัดเจนที่เร็วที่สุดตามอย่างใกล้ชิดโดยtr sedนอกจากนี้ดูเหมือนว่าการใช้echoจะเร็วกว่าการใช้เล็กน้อย<<<:

$ 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

เนื่องจากความแตกต่างนั้นเล็กน้อยฉันจึงทำการทดสอบด้านบน 10 ครั้งสำหรับแต่ละอันและปรากฎว่าวิธีที่เร็วที่สุดนั้นคือสิ่งที่คุณต้องเริ่มต้นด้วย:

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

อย่างไรก็ตามการเปลี่ยนแปลงนี้เมื่อคุณคำนึงถึงค่าใช้จ่ายในการกำหนดตัวแปรที่นี่การใช้trจะช้ากว่าการแทนที่แบบง่ายเล็กน้อย:

$ 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

ดังนั้นโดยสรุปเมื่อคุณต้องการดูผลลัพธ์ให้ใช้trแต่ถ้าคุณต้องการกำหนดตัวแปรใหม่การใช้คุณสมบัติการจัดการสตริงของเชลล์จะเร็วขึ้นเนื่องจากจะหลีกเลี่ยงโอเวอร์เฮดของการเรียกใช้ subshell แยก


4
เนื่องจาก OP มีความสนใจในการตั้งค่าที่ปรับเปลี่ยนกลับเข้ามาOUTPUTคุณจะต้องพิจารณาค่าใช้จ่ายของ sub-shell sub ทดแทนเชลล์คำสั่งที่เกี่ยวข้องtrและsedวิธีแก้ปัญหา
iruvar

@ 1_CR ใช่ แต่เนื่องจากเป็นกรณีใดที่วิธีการที่เขาใช้ฉันคิดว่ามันไม่เกี่ยวข้อง
terdon

1
ไม่ค่อนข้างOUTPUT="${OUTPUT//[`\"\']/}" ไม่เกี่ยวข้องกับการทดแทนคำสั่ง
iruvar

@ 1_CR อ่าเข้าใจแล้วใช่คุณพูดถูกและเปลี่ยนผลลัพธ์ ขอบคุณตอบแก้ไข
terdon

2
วิธีการที่เกี่ยวข้องกับการทดแทนคำสั่งมีข้อเสียของการค่อนข้าง mangling สตริง (คุณสามารถหลีกเลี่ยงได้ แต่ค่าใช้จ่ายในการทำให้คำสั่งมีความซับซ้อนมากขึ้น) โดยเฉพาะการทดแทนคำสั่งจะลบบรรทัดใหม่ต่อท้าย
Gilles 'หยุดความชั่วร้าย'

15

คุณสามารถใช้การทดแทนตัวแปร :

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

ใช้ไวยากรณ์นั้น: ${parameter//pattern/string}เพื่อแทนที่รูปแบบที่เกิดขึ้นทั้งหมดด้วยสตริง

$ 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}ให้axbxcxa
ความโกลาหล

ชื่อการขยาย "ตัวแปรส่วนขยาย" ไม่ถูกต้อง มันถูกเรียกว่า "การขยายพารามิเตอร์"
gena2x

@ gena2x - ฉันไม่เข้าใจความคิดเห็นของคุณหมายความว่าอะไรที่นี่?
slm

12

ใน bash หรือ zsh คือ:

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

โปรดทราบว่า${VAR//PATTERN/}ลบอินสแตนซ์ทั้งหมดของรูปแบบ สำหรับข้อมูลเพิ่มเติมการขยายพารามิเตอร์ bash

โซลูชันนี้ควรเร็วที่สุดสำหรับสตริงสั้น ๆ เนื่องจากไม่เกี่ยวข้องกับการรันโปรแกรมภายนอกใด ๆ อย่างไรก็ตามสำหรับสตริงที่ยาวมาก ๆ สิ่งที่ตรงกันข้ามก็เป็นจริง - มันดีกว่าที่จะใช้เครื่องมือเฉพาะสำหรับการทำงานของข้อความตัวอย่างเช่น:

$ 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
ความจริงแล้วtrเร็วกว่า regexes และ globs trมีราคาแพงและในขณะที่ไม่มีโปรแกรมภายนอกนี่ทุบตีมักจะช้ากว่าสิ่งที่ชอบ
terdon

นั่นขึ้นอยู่กับข้อมูลเข้าและการใช้งาน regexp ในคำตอบของคุณคุณต้องใช้ชุดข้อมูลขนาดใหญ่โดยเฉพาะ - แต่ชุดข้อมูลอาจมีขนาดเล็ก หรือแตกต่างกัน นอกจากนี้คุณวัดไม่ใช่เวลาของ regexp แต่เวลาของเสียงก้องดังนั้นฉันไม่แน่ใจว่าการเปรียบเทียบของคุณเป็นธรรมจริงๆ
gena2x

จุดที่ดี อย่างไรก็ตามคุณไม่สามารถทำการเรียกร้องเกี่ยวกับความเร็วโดยไม่มีการทดสอบ ในความเป็นจริงเมื่อกำหนดให้ตัวแปรดูเหมือนเร็วขึ้น แต่เมื่อพิมพ์ไปที่หน้าจอtrชนะ (ดูคำตอบของฉัน) ฉันยอมรับว่ามันจะขึ้นอยู่กับปัจจัยหลายอย่าง แต่นั่นเป็นเหตุผลว่าทำไมคุณไม่สามารถบอกได้ว่าใครชนะโดยไม่ทำการทดสอบ
terdon

6

หากในกรณีที่คุณไม่ได้ใช้งานคุณเพียงแค่พยายามจัดการอัญประกาศเพื่อนำเปลือกไอออนกลับมาใช้ใหม่คุณสามารถทำได้โดยไม่ต้องลบมันออกและมันก็ง่ายเกินไปเช่นกัน:

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

ฟังก์ชั่นของเชลล์นั้นเสนอราคาอาเรย์หาเรื่องใด ๆ ที่คุณมอบและเพิ่มเอาท์พุทของมันสำหรับแต่ละอาร์กิวเมนต์

ที่นี่มันมี args ไม่กี่:

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

เอาท์พุท

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

ผลลัพธ์ที่ได้จากซึ่งมักจะปลอดภัยคำพูดเอาท์พุทเดียวที่ยกมาเช่นdash จะทำอย่างไร '"'"'bash'\''

การเปลี่ยนการเลือกเดียวที่ไม่ใช่ช่องว่างที่ไม่ใช่ null ไบต์กับอีก byte เดียวจะสามารถทำได้เร็วที่สุดใน POSIX เปลือกและ$IFS$*

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

เอาท์พุท

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

ที่นั่นฉันเพียงแค่printfมันเพื่อให้คุณสามารถดู แต่แน่นอนถ้าฉันได้ทำ:

var="$*"

... แทนที่จะเป็นค่าของprintfคำสั่ง$varจะเป็นสิ่งที่คุณเห็นในผลลัพธ์ที่นั่น

เมื่อฉันset -fฉันสั่งเปลือกไม่ให้ glob - ในกรณีที่สตริงมีตัวอักษรที่สามารถตีความได้ว่าเป็นรูปแบบ glob ฉันทำเช่นนี้เพราะ shell parser ขยายรูปแบบ glob หลังจากที่ดำเนินการแยกฟิลด์กับตัวแปร globbing set +fสามารถเปิดใช้งานเช่น โดยทั่วไป - เป็นสคริปต์ - ฉันพบว่ามีประโยชน์ในการตั้งค่าปังของฉันเช่น:

#!/usr/bin/sh -f

และจากนั้นให้เปิดใช้งาน globbing อย่างชัดเจนกับset +fสิ่งที่ฉันอาจต้องการมัน

$IFSแยกสนามเกิดขึ้นบนพื้นฐานของตัวละครใน

มีสองประเภทของ$IFSค่าคือ - $IFSช่องว่างและช่องว่างที่$IFSไม่ใช่ $IFSช่องว่างที่คั่นด้วยช่องว่าง(ช่องว่าง, แท็บบรรทัดใหม่)ถูกระบุให้ตัดตามลำดับในฟิลด์เดียว(หรือไม่มีเลยถ้าพวกเขาไม่นำหน้าอย่างอื่น) - ดังนั้น ...

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

แต่คนอื่น ๆ ทั้งหมดจะถูกระบุให้ประเมินเป็นเขตข้อมูลเดียวต่อเหตุการณ์ที่เกิดขึ้น - พวกเขาจะไม่ถูกตัดทอน

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

ทั้งหมดขยายตัวแปรตามค่าเริ่มต้น$IFSที่คั่นอาร์เรย์ข้อมูล - $IFSพวกเขาแยกออกไปแยกทุ่งตาม เมื่อคุณ - "พูดถึงสิ่งที่คุณแทนที่คุณสมบัติอาร์เรย์นั้นและประเมินเป็นสตริงเดียว

ดังนั้นเมื่อฉันทำ ...

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

ฉันกำลังตั้งค่าอาเรย์อาร์กิวเมนต์ของเชลล์ไปยัง$IFSฟิลด์ที่มีตัวคั่นจำนวนมากซึ่งเกิดจาก$varการขยายตัวของ เมื่อได้มีการขยายค่าที่เป็นส่วนประกอบของตัวละครที่มีอยู่ใน$IFSจะหายไป - พวกเขาเป็นเพียงตัวคั่นฟิลด์ในขณะนี้ - \0NULพวกเขามี

"$*"- เหมือนยกมาสองครั้งตัวแปรขยายอื่น ๆ - $IFSยังแทนที่คุณภาพฟิลด์แยกของ แต่นอกเหนือจากนั้นมันทดแทนไบต์แรกใน$IFS แต่ละสนามคั่น"$@"ใน ดังนั้นเพราะ"เป็นครั้งแรกที่ค่าใน$IFS ทุกตัวคั่นต่อมากลายเป็นใน" "$*"และ"ไม่จำเป็นต้องอยู่ใน$IFSเมื่อคุณแยกมันเช่นกัน คุณสามารถปรับเปลี่ยน$IFS หลังจาก set -- $argsค่าอื่นทั้งหมดและของใหม่"$*"ไบต์แรกจากนั้นก็จะแสดงขึ้นสำหรับตัวคั่นสนามใน ยิ่งไปกว่านั้นคุณสามารถลบร่องรอยทั้งหมดเช่น:

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

เอาท์พุท

some crazy string here

ดีมาก +1 ฉันสงสัยว่ามันเร็วขึ้นแน่นอน คุณสามารถเพิ่มการทดสอบเวลาในการเปรียบเทียบกับแนวทางในคำตอบของฉันได้หรือไม่? ฉันหวังว่าคุณจะเร็วขึ้น แต่อยากจะเห็น
terdon

@terdon - ขึ้นอยู่กับเชลล์ มันเกือบจะเร็วกว่าtrในทุกเชลล์แน่นอน แต่ความแตกต่างนั้นไม่แน่นอนbashสำหรับ${var//$c/$newc/}เคส ฉันคาดหวังว่าแม้ในกรณีนั้นมันจะเร็วขึ้นด้วยกำไรบางส่วน แต่ฉันไม่ต้องกังวลเกี่ยวกับสิ่งนั้นเพราะสำหรับสิ่งนี้ฉันมักจะใช้dash- ซึ่งเร็วกว่าคำสั่งของขนาดโดยทั่วไปทุกประการ ดังนั้นจึงเป็นการยากที่จะเปรียบเทียบ
mikeserv

@terdon - ฉันพยายามแล้ว แต่ - แม้กระทั่ง - ในbash- ทำtime (IFS=\"\'`; set -- $var; printf %s "$*")และtime (var=${var//\'`/\"/})ทั้งสองผลลัพธ์ใน0.0000sฟิลด์ทั้งหมด ฉันคิดผิดหรือเปล่า มันควรจะเป็นแบ็กสแลชก่อนที่ backquote จะอยู่ที่นั่น แต่ฉันไม่รู้วิธีใส่ backquote ลงในช่องรหัสความคิดเห็น
mikeserv
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.