刪除變數中字元的最短方法

刪除變數中字元的最短方法

有多種方法可以刪除變數中的字元。

到目前為止我發現的最短的方法是tr:

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

有更快的方法嗎?

'對於,"及其本身這樣的引號來說,這種引用安全嗎`

答案1

讓我們來看看。我能想到的最短的是對您的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。另外,看起來 usingecho實際上比 using 稍快<<<

$ 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但如果您想重新指派給變量,則使用 shell 的字串操作功能會更快,因為它們避免了執行單獨的子 shell 的開銷。

答案2

你可以使用變數替換:

$ 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

答案3

在 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

答案4

如果偶爾您只是想處理重複使用 shell 的引號,那麼您可以這樣做沒有刪除它們,也非常簡單:

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

這個函數 shell 引用您傳遞給它的任何 arg 數組,並增加每個可迭代參數的輸出。

這裡有一些參數:

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會做'\''

$IFS在使用和的任何 POSIX shell 中,將選定的單一非空白、非空位元組替換為另一個單一位元組可能是最快的$*

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

輸出

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

我只是把printf它放在那裡,以便你可以看到它,但當然,如果我這樣做了:

var="$*"

...而不是printf命令$var的值將是您在輸出中看到的值。

當我set -f指示 shell時不是to glob - 如果字串包含可以解釋為 glob 模式的字元。我這樣做是因為 shell 解析器擴展了 glob 模式它對變數執行字段分割。可以像 一樣重新啟用通配符set +f。一般來說 - 在腳本中 - 我發現將我的瀏海設定如下很有用:

#!/usr/bin/sh -f

然後到明確啟用通配符set +f我可能想要的任何線路有關。

字段分割是根據 中的字元進行的$IFS

有兩種$IFS值 -$IFS空白和$IFS非空白。$IFS空白(空格、製表符、換行符)分隔欄位指定為省略順序到單一字段(或者如果它們不先於其他內容,則根本沒有)- 所以...

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

但所有其他的都被指定為評估單一字段每次出現- 它們沒有被截斷。

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

全部預設情況下,變數擴展是$IFS分隔資料數組 - 它們根據$IFS.當您用"-quote 引用一個時,您將覆寫該數組屬性並將其計算為單一字串。

所以當我這樣做時...

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

我將 shell 的參數數組設定為由的擴展$IFS生成的許多分隔字段。$var當它被擴展時,它所包含的字元的組成$IFS值為遺失的- 它們現在只是字段分隔符號 - 它們是\0NUL

"$*"- 與其他雙引號變數擴展一樣 - 也覆蓋$IFS.但,另外,它替換中的第一個位元組$IFS 對於每個分隔字段"$@"。所以因為"第一的值在$IFS 所有後續分隔符號都"變成"$*".當你拆分它時,也不必"在其中。$IFS你可以改變$IFS set -- $args完全到另一個值及其新的然後第一個位元組將顯示為 中的欄位分隔符號"$*"。更重要的是,您可以完全刪除它們的所有痕跡,如下所示:

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

輸出

some crazy string here

相關內容