найкоротший спосіб заміни символів у змінній


18

Існує багато способів заміни символів у змінній.

Найкоротший шлях, який я дізнався, trпоки що:

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

Чи є швидший шлях? І це з посиланням на безпечні цитати , як ', "і `саме по собі?


Я думаю, ви можете продовжувати використовувати tr. ПЕ на BASH хороший, але в цьому випадку tr набагато швидше. наприклад, echo "$OUTPUT" | tr -dc '[[:alpha:]]' оскільки ви хочете мати лише буквено-цифрові слова
Валентин Байрамі

2
Оскільки ви зацікавлені в цікавості, що цитує: Завжди цитуйте свої змінні! echo "$OUTPUT". Або краще : printf "%s\n" "$OUTPUT". (Що станеться, коли OUTPUT="-n"?)
musiphil

Ви також можете розглянути перегляд кодового гольфу , особливо поради щодо баш .
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, вам доведеться враховувати накладні підрозділи підстановки команд, що trsed
входять

@ 1_CR так, але оскільки це буде саме той метод, який він використовує, я вважав, що це не має значення.
terdon

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 - я не розумію, що тут означає ваш коментар?
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швидше. Регекси та глобуси коштують дорого, і хоча тут немає зовнішньої програми, баш завжди буде повільніше, ніж щось подібне tr.
тердон

Це сильно залежить від вхідних даних та від впровадження регулярного вибору. У своїй відповіді ви взяли певний великий набір даних - але набір даних може бути малим. Або по-іншому. Крім того, ви вимірюєте не час regexp, а час відлуння, тому я не можу бути впевнений, чи ваше порівняння справді справедливе.
gena2x

Хороші бали. Однак ви не можете пред'являти претензії щодо швидкості без тестування. Насправді, при призначенні змінної це здається швидшим, але коли друк на екрані trвиграє (див. Мою відповідь). Я погоджуюся, що це буде залежати від багатьох факторів, але саме тому ви не можете сказати, який з них виграє, фактично не тестуючи його.
тердон

6

Якщо, за випадковості, ви просто намагаєтеся обробити цитати для повторного використання іонної оболонки, тоді ви можете це зробити, не виймаючи їх, і це теж просто:

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

Ця оболонка функції цитує будь-який аргумент масиву, який ви йому передаєте, і збільшує його вихід на ітерабельний аргумент.

Ось це з кількома аргументами:

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було б те, що ви бачите у висновку там.

Коли я set -fдоручаю оболонці не глобувати - на випадок, якщо рядок містить символи, які можуть бути розроблені як шаблони глобуса. Я роблю це через те, що аналізатор оболонок розширює глобальні шаблони після того, як він здійснює розбиття поля на змінні. глобус можна повторно включити, як 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. Коли ви "цитуєте один, ви переосмислюєте цей властивість масиву і оцінюєте його як єдиний рядок.

Тож коли я це роблю ...

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результатів для всіх сфер. Чи я щось роблю не так, як ви думаєте? Перед зворотним котируванням має бути зворотна косою рисою, але я не знаю, як поставити зворотне котирування в поле коду коментаря.
mikeserv
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.