cách ngắn nhất để thay thế các ký tự trong một biến


18

Có nhiều cách để thay thế các ký tự trong một biến.

Cách ngắn nhất tôi tìm ra là trcho đến nay:

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

Có cách nào nhanh hơn? Và trích dẫn này có an toàn cho các trích dẫn như ', "và chính nó không?


Tôi nghĩ bạn có thể tiếp tục sử dụng tr. PE của BASH tốt nhưng tr nhanh hơn nhiều trong trường hợp này. ví dụ: echo "$OUTPUT" | tr -dc '[[:alpha:]]' vì bạn chỉ muốn có chữ và số
Valentin Bajrami

2
Vì bạn quan tâm đến việc trích dẫn hiểu biết: Luôn trích dẫn các biến của bạn! echo "$OUTPUT". Hoặc tốt hơn : printf "%s\n" "$OUTPUT". (Chuyện gì xảy ra khi OUTPUT="-n"nào?)
musiphil

Bạn cũng có thể xem xét duyệt codegolf , đặc biệt là các mẹo bash .
hoosierEE

Câu trả lời:


22

Hãy xem nào. Điều ngắn nhất tôi có thể đưa ra là một tinh chỉnh trgiải pháp của bạn :

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

Các lựa chọn thay thế khác bao gồm thay thế biến đã được đề cập có thể ngắn hơn hiển thị cho đến nay:

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

sedtất nhiên mặc dù điều này dài hơn về các nhân vật:

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

Tôi không chắc chắn nếu bạn có nghĩa là ngắn nhất về chiều dài hoặc về thời gian thực hiện. Về chiều dài, hai cái này ngắn như nó có được (hoặc như tôi có thể lấy nó bằng mọi cách) khi nói đến việc loại bỏ các ký tự cụ thể đó. Vậy, cái nào nhanh nhất? Tôi đã kiểm tra bằng cách đặt OUTPUTbiến thành những gì bạn có trong ví dụ của mình nhưng lặp lại vài chục lần:

$ 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

Như bạn có thể thấy, trrõ ràng là nhanh nhất, theo sát bởi sed. Ngoài ra, có vẻ như việc sử dụng echothực sự nhanh hơn một chút so với sử dụng <<<:

$ 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

Vì sự khác biệt là rất nhỏ, tôi đã thực hiện các bài kiểm tra trên 10 lần cho mỗi trong hai và hóa ra rằng nhanh nhất thực sự là bài kiểm tra mà bạn phải bắt đầu:

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

Tuy nhiên, điều này thay đổi khi bạn tính đến chi phí chung của việc gán cho một biến, ở đây, sử dụng trchậm hơn một chút so với thay thế đơn giản:

$ 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

Vì vậy, để kết luận, khi bạn chỉ muốn xem kết quả, hãy sử dụng trnhưng nếu bạn muốn gán lại cho một biến, sử dụng các tính năng thao tác chuỗi của shell sẽ nhanh hơn vì chúng tránh được chi phí chạy một lớp con riêng biệt.


4
Vì OP quan tâm đến việc thiết lập lại giá trị đã sửa đổi OUTPUT, bạn sẽ phải tính đến chi phí thay thế vỏ phụ liên quan đến các giải pháp trsedcác giải pháp
iruvar

@ 1_CR có nhưng vì đó sẽ là trường hợp anh ấy sử dụng phương pháp nào, tôi cho rằng nó không liên quan.
terdon

1
Không hoàn toàn, OUTPUT="${OUTPUT//[`\"\']/}" không liên quan đến thay thế lệnh
iruvar

@ 1_CR ah, tôi thấy, vâng, bạn hoàn toàn đúng và điều đó thay đổi kết quả. Cảm ơn, trả lời chỉnh sửa.
terdon

2
Các phương thức liên quan đến thay thế lệnh có nhược điểm là xáo trộn chuỗi. (Bạn có thể tránh nó nhưng với chi phí làm cho lệnh phức tạp hơn đáng kể.) Đặc biệt, thay thế lệnh sẽ loại bỏ các dòng mới.
Gilles 'SO- ngừng trở nên xấu xa'

15

Bạn có thể sử dụng thay thế biến :

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

Sử dụng cú pháp đó: ${parameter//pattern/string}để thay thế tất cả các lần xuất hiện của mẫu bằng chuỗi.

$ 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}choaxbxcxa
sự hỗn loạn

Không đúng khi đặt tên cho phần mở rộng là "mở rộng biến". Nó được gọi là "mở rộng tham số".
gena2x

@ gena2x - Tôi không hiểu ý kiến ​​của bạn ở đây là gì?
slm

12

Trong bash hoặc zsh, nó là:

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

Lưu ý rằng ${VAR//PATTERN/}loại bỏ tất cả các trường hợp của mẫu. Để biết thêm thông tin mở rộng tham số bash

Giải pháp này phải nhanh nhất cho các chuỗi ngắn vì nó không liên quan đến việc chạy bất kỳ chương trình bên ngoài nào. Tuy nhiên, đối với các chuỗi rất dài thì điều ngược lại là đúng - tốt hơn là sử dụng công cụ chuyên dụng cho các hoạt động văn bản, ví dụ:

$ 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
Trong thực tế, trlà nhanh hơn. Regexes và globs là đắt tiền, và trong khi không có chương trình bên ngoài ở đây, bash sẽ luôn luôn chậm hơn một cái gì đó như tr.
terdon

Điều đó phụ thuộc rất nhiều vào dữ liệu đầu vào và vào việc thực hiện regrec. Trong câu trả lời của bạn, bạn đã lấy một số tập dữ liệu lớn cụ thể - nhưng tập dữ liệu có thể nhỏ. Hoặc khác nhau. Ngoài ra, bạn đo không phải thời gian của regrec mà là thời gian của tiếng vang, vì vậy tôi không thể chắc chắn nếu so sánh của bạn thực sự công bằng.
gena2x

Điểm tốt. Tuy nhiên, bạn không thể đưa ra yêu cầu về tốc độ mà không cần kiểm tra. Trong thực tế, khi gán cho một biến, điều này có vẻ nhanh hơn nhưng khi in ra màn hình trsẽ thắng (xem câu trả lời của tôi). Tôi đồng ý rằng nó sẽ phụ thuộc vào nhiều yếu tố nhưng đó chính xác là lý do tại sao bạn không thể biết ai thắng mà không thực sự kiểm tra nó.
terdon

6

Nếu, trong trường hợp không may, bạn chỉ đang cố gắng xử lý các trích dẫn để tái sử dụng ion vỏ, thì bạn có thể làm điều này mà không cần loại bỏ chúng, và nó cũng rất đơn giản:

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

Hàm shell đó trích dẫn bất kỳ mảng arg nào bạn trao cho nó và tăng đầu ra của nó cho mỗi đối số có thể lặp lại.

Đây là với một vài lập luận:

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

ĐẦU RA

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

Đầu ra đó là từ dashmà thông thường trích dẫn an toàn trích dẫn đầu ra như thế '"'"'. bashsẽ làm gì '\''.

Việc thay thế một lựa chọn các byte đơn, không phải khoảng trắng, không null bằng một byte đơn khác có thể được thực hiện nhanh nhất trong bất kỳ shell POSIX nào bằng $IFS$*.

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

ĐẦU RA

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

Ở đó tôi chỉ printfcó nó để bạn có thể nhìn thấy nó, nhưng tất nhiên, nếu tôi đã làm:

var="$*"

... Thay vì giá trị của printflệnh $varsẽ là những gì bạn thấy trong đầu ra ở đó.

Khi tôi set -fchỉ thị shell không phải toàn cầu - trong trường hợp chuỗi chứa các ký tự có thể được hiểu là các mẫu hình cầu. Tôi làm điều này bởi vì trình phân tích cú pháp shell sẽ mở rộng các mẫu toàn cục sau khi nó thực hiện phân tách trường trên các biến. Globing có thể được kích hoạt lại như thế nào set +f. Nói chung - trong các tập lệnh - tôi thấy hữu ích khi đặt bang của mình như:

#!/usr/bin/sh -f

Và sau đó để kích hoạt toàn cầu với set +fbất kỳ dòng nào tôi có thể muốn.

Trường tách xảy ra dựa trên các ký tự trong $IFS.

Có hai loại $IFSgiá trị - $IFSkhoảng trắng và $IFSkhông khoảng trắng. $IFSCác trường được phân cách bằng khoảng trắng ( dấu cách, tab, dòng mới) được chỉ định để tách biệt theo trình tự thành một trường duy nhất (hoặc không có gì cả nếu chúng không đi trước một cái gì khác) - vì vậy ...

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

Nhưng tất cả những thứ khác được chỉ định để đánh giá một trường duy nhất cho mỗi lần xuất hiện - chúng không bị cắt cụt.

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

Theo mặc định, tất cả các mở rộng biến là $IFScác mảng dữ liệu được phân tách - chúng phân tách thành các trường riêng biệt theo $IFS. Khi bạn "trích dẫn một, bạn ghi đè lên thuộc tính mảng đó và đánh giá nó dưới dạng một chuỗi.

Vì vậy, khi tôi làm ...

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

Tôi đang đặt mảng đối số của shell thành nhiều $IFStrường được phân tách bằng cách $varmở rộng. Khi nó được mở rộng, các giá trị cấu thành của nó cho các ký tự chứa trong $IFSđó bị mất - hiện tại chúng chỉ là các dấu tách trường - chúng là \0NUL.

"$*"- giống như các mở rộng biến được trích dẫn kép khác - cũng ghi đè lên các phẩm chất phân tách trường của $IFS. Nhưng, ngoài ra , nó thay thế byte đầu tiên $IFS cho mỗi trường được phân tách trong "$@". Vì vậy, vì "lần đầu tiên giá trị trong $IFS tất cả các delimiters tiếp theo trở nên "trong "$*". "không cần thiết $IFSkhi bạn chia nó. Bạn có thể thay đổi hoàn toàn $IFS sau set -- $args một giá trị khác và byte đầu tiên mới của nó sau đó sẽ hiển thị cho các dấu phân cách trường trong "$*". Hơn thế nữa, bạn có thể xóa tất cả dấu vết của chúng hoàn toàn như:

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

ĐẦU RA

some crazy string here

Rất đẹp, +1. Tôi tự hỏi nếu nó thực sự nhanh hơn. Bạn có thể thêm một số bài kiểm tra thời gian so sánh nó với các phương pháp tiếp cận trong câu trả lời của tôi không? Tôi hy vọng bạn sẽ nhanh hơn nhưng muốn xem.
terdon

@terdon - điều đó phụ thuộc vào vỏ. Đó là gần như chắc chắn nhanh hơn trtrong bất kỳ vỏ, nhưng sự khác biệt là iffy trong bashcho ${var//$c/$newc/}trường hợp. Tôi hy vọng ngay cả trong trường hợp đó sẽ nhanh hơn bởi một số lợi nhuận, nhưng tôi thường không lo lắng về điều đó bởi vì đối với công cụ này tôi luôn sử dụng dash- nhanh hơn bởi các đơn đặt hàng cường độ nói chung ở mọi khía cạnh. Và vì vậy thật khó để so sánh.
mikeerv

@terdon - Tôi đã thử. Nhưng - ngay cả trong bash- đang làm time (IFS=\"\'`; set -- $var; printf %s "$*")time (var=${var//\'`/\"/})cả hai đều dẫn đến 0.0000skết quả cho tất cả các lĩnh vực. Tôi đang làm gì đó sai, bạn có nghĩ? Có một dấu gạch chéo ngược trước backquote ở đó nhưng tôi không biết làm thế nào để đặt backquote trong trường mã nhận xét.
mikeerv
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.