кратчайший способ заменить символы в переменной

18

Есть много способов заменить символы в переменной.

Кратчайший путь, который я обнаружил, trэто пока:

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

Есть ли более быстрый способ? И это со ссылкой на безопасные цитаты , как ', "и `само по себе?

rubo77
источник
Я думаю, что вы можете продолжать использовать tr. PE BASH хорош, но в этом случае tr намного быстрее. например, echo "$OUTPUT" | tr -dc '[[:alpha:]]' поскольку вы хотите, чтобы были только буквенно-цифровые символы
Валентин Байрами
2
Так как вы заинтересованы в умении цитировать: всегда указывайте свои переменные! echo "$OUTPUT", Или лучше: printf "%s\n" "$OUTPUT". (Что происходит, когда OUTPUT="-n"?)
musiphil
Вы могли бы также рассмотреть просмотр codegolf , особенно подсказки bash .
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но если вы хотите переназначить переменную, использование функций манипуляции со строками оболочки выполняется быстрее, поскольку они избегают накладных расходов при запуске отдельной подоболочки.

Тердон
источник
4
Поскольку ОП заинтересована в установлении модифицированное значение обратно в OUTPUT, вы должны учитывать команды замещения подоболочку накладные расходы , связанные trи sedрешения
Iruvar
@ 1_CR Да, но так будет и в зависимости от того, какой метод он использует, я решил, что он не имеет значения.
Тердон
1
Не совсем, OUTPUT="${OUTPUT//[`\"\']/}" не подразумевает подстановку команд
iruvar
@ 1_CR ах, понятно, ты прав, и это меняет результат. Спасибо, ответ отредактирован.
Terdon
2
Методы, которые включают подстановку команд, имеют недостаток, заключающийся в некотором искажении строки. (Вы можете избежать этого, но за счет усложнения команды.) В частности, подстановка команд удаляет завершающие символы новой строки.
Жиль "ТАК - перестань быть злым"
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 - я не понимаю, что означает здесь ваш комментарий?
СЛМ
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
gena2x
источник
1
На самом деле, trбыстрее. Регулярные выражения и глобусы дороги, и, хотя здесь нет внешней программы, bash всегда будет работать медленнее, чем что-то подобное tr.
Тердон
Это сильно зависит от входных данных и от реализации регулярных выражений. В своем ответе вы взяли какой-то конкретный большой набор данных - но набор данных может быть небольшим. Или другой. Кроме того, вы измеряете не время регулярного выражения, а время эха, поэтому я не могу быть уверен, что ваше сравнение действительно справедливо.
gena2x
Хорошие моменты. Тем не менее, вы не можете претендовать на скорость без тестирования. На самом деле, при назначении переменной это кажется быстрее, но при печати на экране trвыигрывает (см. Мой ответ). Я согласен, что это будет зависеть от многих факторов, но именно поэтому вы не можете сказать, какой из них выиграет, фактически не проверяя его.
Тердон
6

Если, по случайности, вы просто пытаетесь обработать кавычки для повторного использования оболочки, то вы можете сделать это, не удаляя их, и это тоже очень просто:

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

Эта функциональная оболочка заключает в кавычки любой массив 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сделал бы '\''.

Замена выделенного, непустого, ненулевого байта другим одиночным байтом, вероятно, может быть выполнена быстрее всего в любой оболочке POSIX с помощью $IFSи $*.

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

ВЫХОД

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

Там я просто printfтак, чтобы вы могли видеть это, но, конечно, если бы я сделал:

var="$*"

... вместо printfкоманды $varзначение «s будет то , что вы видите на выходе там.

Когда я set -fдаю команду оболочке не использовать глобус - в случае, если строка содержит символы, которые могут быть истолкованы как шаблоны глобуса. Я делаю это потому, что синтаксический анализатор оболочек расширяет шаблоны глобуса после того, как он выполняет разбиение поля на переменные. Globbing может быть снова включен, как set +f. В целом - в сценариях - я считаю полезным установить мой взрыв следующим образом:

#!/usr/bin/sh -f

И затем, чтобы явно разрешить глобализацию с set +fлюбой строкой, которую я мог бы хотеть.

Разделение полей происходит на основе символов в $IFS.

Есть два вида $IFSзначений - $IFSпробельные и $IFSнепробельные. $IFSпробельных (пробел, табуляция, перевод строки) с разделителями полей указаны в Elide по последовательности к одному полю (или вообще ничего , если они не предшествуют что - то еще) - так ...

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
mikeserv
источник
Очень приятно +1. Интересно, действительно ли это быстрее? Не могли бы вы добавить некоторые временные тесты, сравнивая это с подходами в моем ответе? Я надеюсь, что ваш будет быстрее, но хотел бы видеть.
Terdon
@terdon - это зависит от оболочки. Это почти определенно быстрее, чем trв любой оболочке, но разница в bashданном ${var//$c/$newc/}случае сомнительна . Я ожидаю, что даже в этом случае это будет быстрее с некоторым запасом, но я обычно не беспокоюсь об этом, потому что для этого материала я всегда использую dash- который быстрее на порядок во всех отношениях. И так сложно сравнивать.
mikeserv
@terdon - Я пытался. Но - даже в работе bash, time (IFS=\"\'`; set -- $var; printf %s "$*")и time (var=${var//\'`/\"/})оба приводят к 0.0000sрезультатам для всех полей. Я что-то делаю не так, как вы думаете? Там должна быть обратная косая черта перед обратной цитатой, но я не знаю, как вставить обратную цитату в поле кода комментария.
mikeserv