web-dev-qa-db-fra.com

moyen le plus court de remplacer des caractères dans une variable

Il existe de nombreuses façons de remplacer des caractères dans une variable.

Le moyen le plus court que j'ai découvert est tr jusqu'à présent:

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

Y at-il un moyen plus rapide? Et est-ce sûr pour les citations comme ', " et `lui-même?

19
rubo77

Voyons voir. Le plus court que je puisse trouver est un Tweak de votre solution tr:

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

D'autres alternatives incluent la substitution de variables déjà mentionnée qui peut être plus courte que celle illustrée jusqu'à présent:

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

Et sed bien sûr, bien que ce soit plus long en termes de caractères:

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

Je ne sais pas si vous voulez dire la durée la plus courte ou en termes de temps pris. En termes de longueur, ces deux sont aussi courts que possible (ou comme je peux l'obtenir de toute façon) quand il s'agit de supprimer ces caractères spécifiques. Alors, quel est le plus rapide? J'ai testé en définissant la variable OUTPUT sur ce que vous aviez dans votre exemple, mais répété plusieurs dizaines de fois:

$ 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

Comme vous pouvez le voir, le tr est clairement le plus rapide, suivi de près par sed. De plus, il semble que l'utilisation de echo soit en fait légèrement plus rapide que celle de <<<:

$ 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

Étant donné que la différence est minuscule, j'ai exécuté les tests ci-dessus 10 fois pour chacun des deux et il s'avère que le plus rapide est en effet celui avec lequel vous avez dû commencer:

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

Cependant, cela change lorsque vous prenez en compte la surcharge d'affectation à une variable, ici, l'utilisation de tr est légèrement plus lente que le remplacement simple:

$ 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

Donc, en conclusion, lorsque vous souhaitez simplement afficher les résultats, utilisez tr mais si vous souhaitez réaffecter à une variable, l'utilisation des fonctionnalités de manipulation de chaîne du Shell est plus rapide car elles évitent la surcharge d'exécution d'un sous-shell séparé.

22
terdon

Vous pouvez utiliser substitution de variable :

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

Utilisez cette syntaxe: ${parameter//pattern/string} pour remplacer toutes les occurrences du motif par la chaîne.

$ echo "${OUTPUT//\'/x}"
axb"c`d
$ echo "${OUTPUT//\"/x}"
a'bxc`d
$ echo "${OUTPUT//\`/x}"
a'b"cxd
$ echo "${OUTPUT//[\'\"\`]/x}"
axbxcxd
15
chaos

En bash ou zsh c'est:

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

Notez que ${VAR//PATTERN/} supprime toutes les instances du modèle. Pour plus d'informations expansion des paramètres bash

Cette solution devrait être plus rapide pour les chaînes courtes car elle n'implique pas l'exécution de programmes externes. Cependant, pour les très longues chaînes, l'inverse est vrai - il est préférable d'utiliser un outil dédié pour les opérations de texte, par exemple:

$ 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
12
gena2x

Si, par hasard, vous essayez simplement de gérer les devis pour la réutilisation du Shell, alors vous pouvez le faire sans les supprimer, et c'est aussi simple que cela:

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

Cette fonction Shell cite tout tableau d'arguments que vous lui donnez et incrémente sa sortie par argument itérable.

Le voici avec quelques arguments:

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

PRODUCTION

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

Cette sortie provient de dash, qui cite généralement les sorties entre guillemets simples comme '"'"'. bash ferait '\''.

Le remplacement d'une sélection d'octets simples, non blancs et non nuls par un autre octet unique peut probablement se faire le plus rapidement dans n'importe quel shell POSIX avec $IFS Et $*.

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

PRODUCTION

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

Là je l'ai juste printf pour que vous puissiez le voir, mais bien sûr, si je l'avais fait:

var="$*"

... plutôt que la valeur de la commande printf$var serait ce que vous voyez dans la sortie là-bas.

Quand je set -f J'instruis le Shell pas à glob - au cas où la chaîne contient des caractères qui pourraient être interprétés comme des motifs glob. Je le fais parce que l'analyseur de shells étend les modèles globaux après il effectue la division du champ sur les variables. la globalisation peut être réactivée comme set +f. En général - dans les scripts - je trouve utile de définir mon bang comme:

#!/usr/bin/sh -f

Et puis activer explicitement la globalisation avec set +f Sur la ligne que je pourrais vouloir.

La division des champs se produit en fonction des caractères dans $IFS.

Il existe deux types de valeurs $IFS - $IFS Espaces blancs et $IFS Non blancs. $IFS Espaces blancs (espace, tabulation, nouvelle ligne) les champs délimités sont spécifiés pour être éliminés par séquence vers un seul champ (ou aucun du tout si ils ne précèdent pas autre chose) - donc ...

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

Mais tous les autres sont spécifiés pour être évalués sur un seul champ par occurrence - ils ne sont pas tronqués.

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

Tous les extensions de variables sont, par défaut, des tableaux de données délimités par $IFS - ils se répartissent en champs séparés selon $IFS. Lorsque vous " - en citez une, vous remplacez cette propriété de tableau et l'évaluez comme une chaîne unique.

Alors quand je fais ...

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

Je mets le tableau d'arguments du Shell sur les nombreux champs délimités par $IFS Générés par l'expansion de $var. Lorsqu'il est développé, ses valeurs constitutives pour les caractères contenus dans $IFS Sont perdues - ce ne sont plus que des séparateurs de champs - ce sont \0NUL.

"$*" - comme les autres extensions de variable entre guillemets doubles - remplace également les qualités de division de champ de $IFS. Mais, en plus, il remplace le premier octet dans $IFSpour chaque champ délimité dans "$@". Donc parce que " Était la valeur première dans $IFStous les délimiteurs suivants deviennent " Dans "$*". = Et le " N'a pas besoin d'être dans $IFS Lorsque vous le divisez non plus. Vous pouvez modifier $IFSaprèsset -- $args En une autre valeur et son nouvea premier octet apparaîtra alors pour les délimiteurs de champ dans "$*". De plus, vous pouvez en supprimer toutes les traces comme:

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

PRODUCTION

some crazy string here
6
mikeserv