kürzeste Möglichkeit, Zeichen in einer Variablen zu ersetzen

18

Es gibt viele Möglichkeiten, Zeichen in einer Variablen zu ersetzen.

Der kürzeste Weg, den ich herausgefunden habe, ist trbisher:

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

Gibt es einen schnelleren Weg? Und dann ist dies unter Angabe sicher für Angebote wie ', "und `selbst?

rubo77
quelle
Ich denke, Sie können weiterverwenden tr. BASHs PE ist gut, aber in diesem Fall ist tr viel schneller. zB echo "$OUTPUT" | tr -dc '[[:alpha:]]' weil Sie nur alphanumerische Zeichen haben möchten
Valentin Bajrami
2
Da Sie sich für Quoting interessieren: Zitieren Sie immer Ihre Variablen! echo "$OUTPUT". Oder besser: printf "%s\n" "$OUTPUT". (Was passiert wenn OUTPUT="-n"?)
musiphil
Sie können auch Codegolf spielen , insbesondere die Bash- Tipps.
Hoosieree

Antworten:

22

Wir werden sehen. Der kürzeste, den ich finden kann, ist eine Optimierung Ihrer trLösung:

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

Andere Alternativen umfassen die bereits erwähnte Variablensubstitution, die kürzer sein kann als bisher gezeigt:

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

Und sednatürlich ist dies in Bezug auf die Charaktere länger:

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

Ich bin mir nicht sicher, ob du die kürzeste Länge oder die kürzeste Zeit meinst. In Bezug auf die Länge sind diese beiden so kurz wie es geht (oder wie ich es sowieso bekommen kann), wenn es darum geht, diese bestimmten Zeichen zu entfernen. Welches ist das schnellste? Ich habe getestet, indem ich die OUTPUTVariable auf das gesetzt habe, was Sie in Ihrem Beispiel hatten, aber einige Dutzend Mal wiederholt habe:

$ 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

Wie Sie sehen, trist der eindeutig der Schnellste, dicht gefolgt von sed. Außerdem ist die Verwendung anscheinend echoetwas schneller als die Verwendung von <<<:

$ 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

Da der Unterschied winzig ist, habe ich die obigen Tests 10 Mal für jeden der beiden durchgeführt und es stellte sich heraus, dass der schnellste tatsächlich der ist, mit dem Sie beginnen mussten:

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

Dies ändert sich jedoch, wenn Sie den Aufwand für die Zuweisung einer Variablen berücksichtigen. Hier ist die Verwendung tretwas langsamer als die einfache Ersetzung:

$ 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

Wenn Sie also nur die Ergebnisse anzeigen möchten, verwenden trSie , aber wenn Sie eine Variable neu zuweisen möchten, ist die Verwendung der Zeichenfolgenmanipulationsfunktionen der Shell schneller, da sie den Aufwand für die Ausführung einer separaten Subshell vermeiden.

terdon
quelle
4
Da das OP daran interessiert ist, den geänderten Wert zurückzusetzen OUTPUT, müssen Sie den Aufwand für die Befehlsersetzung in der trsed
Subshell
@ 1_CR ja, aber da dies unabhängig von der von ihm verwendeten Methode der Fall sein wird, dachte ich, dass dies irrelevant ist.
Terdon
1
Nicht ganz, OUTPUT="${OUTPUT//[`\"\']/}" beinhaltet keine Befehlsersetzung
iruvar
@ 1_CR ah, ich verstehe, ja, du hast recht und das ändert das Ergebnis. Danke, antworte bearbeitet.
Terdon
2
Die Methoden, die eine Befehlsersetzung beinhalten, haben den Nachteil, dass sie die Zeichenfolge etwas entstellen. (Sie können dies vermeiden, allerdings auf Kosten einer erheblichen Komplexisierung des Befehls.) Insbesondere werden durch die Befehlsersetzung nachgestellte Zeilenumbrüche entfernt.
Gilles 'SO- hör auf böse zu sein'
15

Sie können die Variablensubstitution verwenden :

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

Verwenden Sie diese Syntax: ${parameter//pattern/string}um alle Vorkommen des Musters durch die Zeichenfolge zu ersetzen.

$ echo "${OUTPUT//\'/x}"
axb"c`d
$ echo "${OUTPUT//\"/x}"
a'bxc`d
$ echo "${OUTPUT//\`/x}"
a'b"cxd
$ echo "${OUTPUT//[\'\"\`]/x}"
axbxcxd
Chaos
quelle
@ Rubo77 echo ${OUTPUT//[`\"\']/x}gibtaxbxcxa
Chaos
Es ist falsch, die Erweiterung "variable Erweiterung" zu nennen. Es heißt "Parametererweiterung".
Gena2x
@ gena2x - Ich verstehe nicht, was Ihr Kommentar hier bedeutet?
slm
12

In bash oder zsh ist es:

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

Beachten Sie, dass ${VAR//PATTERN/}alle Instanzen des Musters entfernt werden. Weitere Informationen finden Sie in der Bash-Parametererweiterung

Diese Lösung sollte für kurze Zeichenfolgen am schnellsten sein, da keine externen Programme ausgeführt werden müssen. Bei sehr langen Zeichenfolgen ist das Gegenteil der Fall - es ist besser, ein spezielles Tool für Textoperationen zu verwenden, z.

$ 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
quelle
1
In der Tat trist schneller. Regexes und Globs sind teuer, und obwohl es hier kein externes Programm gibt, ist bash immer langsamer als so etwas tr.
terdon
Das hängt stark von den Eingabedaten und der Implementierung von RegExp ab. In Ihrer Antwort haben Sie einen bestimmten großen Datensatz verwendet - der Datensatz kann jedoch klein sein. Oder anders. Außerdem messen Sie nicht die Zeit der Regexp, sondern die Zeit des Echos, sodass ich nicht sicher sein kann, ob Ihr Vergleich wirklich fair ist.
gena2x
Gute Argumente. Sie können jedoch keine Angaben zur Geschwindigkeit machen, ohne diese zu testen. Tatsächlich scheint dies beim Zuweisen zu einer Variablen schneller zu sein, aber beim Drucken auf dem Bildschirm trgewinnt (siehe meine Antwort). Ich bin damit einverstanden, dass es von vielen Faktoren abhängt, aber genau deshalb kann man nicht sagen, welcher gewinnt, ohne ihn tatsächlich zu testen.
Terdon
6

Wenn Sie zufällig nur versuchen, Angebote für die Wiederverwendung der Shell zu verarbeiten, können Sie dies tun, ohne sie zu entfernen, und es ist auch denkbar einfach:

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

Diese Funktionsshell setzt ein beliebiges von Ihnen übergebenes arg-Array in Anführungszeichen und erhöht seine Ausgabe pro iterierbarem Argument.

Hier ist es mit ein paar Argumenten:

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

AUSGABE

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

Diese Ausgabe ist eine Ausgabe, dashdie in der Regel in sicheren Anführungszeichen steht '"'"'. bashwürde tun '\''.

Das Ersetzen einer Auswahl einzelner Bytes, die keine Leerzeichen oder Nullen enthalten, durch ein anderes Byte ist in einer POSIX-Shell mit $IFSund wahrscheinlich am schnellsten möglich $*.

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

AUSGABE

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

Dort habe ich es einfach printfso, dass man es sehen kann, aber natürlich, wenn ich es getan hätte:

var="$*"

... und nicht der Wert des printfBefehls $var, den Sie dort in der Ausgabe sehen.

Wenn ich set -fdie Shell anweise, nicht zu globieren - falls die Zeichenfolge Zeichen enthält, die als Glob-Muster ausgelegt werden könnten. Ich mache das, weil der Shells-Parser Glob-Muster erweitert, nachdem er die Feldaufteilung für Variablen durchgeführt hat. Globbing kann wie wieder aktiviert werden set +f. Im Allgemeinen - in Skripten - finde ich es nützlich, meinen Knall wie folgt zu setzen:

#!/usr/bin/sh -f

Und dann explizit das Globbing mit einer set +fbeliebigen Zeile aktivieren , die ich haben möchte.

Die Feldaufteilung erfolgt anhand der Zeichen in $IFS.

Es gibt zwei Arten von $IFSWerten - $IFSLeerzeichen und $IFSNicht-Leerzeichen. $IFSDurch Leerzeichen (Leerzeichen, Tabulatoren, Zeilenvorschübe) getrennte Felder werden so angegeben, dass sie nach der Reihenfolge in ein einzelnes Feld zerlegt werden (oder gar nicht, wenn sie keinem anderen Feld vorangehen) - also ...

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

Alle anderen sind jedoch so spezifiziert, dass sie pro Vorkommen zu einem einzigen Feld ausgewertet werden - sie werden nicht abgeschnitten.

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

Alle Variablenerweiterungen sind standardmäßig durch $IFSTrennzeichen getrennte Datenfelder $IFS. Sie werden in separate Felder aufgeteilt . Mit "-quote one überschreiben Sie diese Array-Eigenschaft und werten sie als einzelne Zeichenfolge aus.

Also, wenn ich es tue ...

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

Ich setze das Argumentarray der Shell auf die vielen $IFSbegrenzten Felder, die durch $vardie Erweiterung generiert werden . Wenn es seine konstituierenden Werte für die Zeichen erweitert enthalten in $IFSsind verloren - sie sind jetzt nur Feldtrenn - sie sind \0NUL.

"$*"- Wie bei anderen Variablenerweiterungen in doppelten Anführungszeichen - werden auch die feldaufteilenden Eigenschaften von überschrieben $IFS. Aber zusätzlich , ersetzt es das erste Byte in $IFS für jedes Feld begrenzt in "$@". Also da "wurde der erste Wert in $IFS allen nachfolgenden Begrenzern "in "$*". Und das "muss auch nicht sein, $IFSwenn Sie es teilen. Sie können $IFS after set -- $args vollständig auf einen anderen Wert ändern , und das neue erste Byte wird dann für die Feldbegrenzer in angezeigt "$*". Außerdem können Sie alle Spuren wie folgt entfernen:

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

AUSGABE

some crazy string here
mikeserv
quelle
Sehr schön, +1. Ich frage mich, ob es tatsächlich schneller ist. Könnten Sie einige Timing-Tests hinzufügen, die sie mit den Ansätzen in meiner Antwort vergleichen? Ich gehe davon aus, dass deine schneller sein werden, würde es aber gerne sehen.
terdon
@terdon - das kommt auf die Shell an. Es ist fast definitiv schneller als trin jeder anderen Shell, aber der Unterschied ist zweifelhaft bashfür den ${var//$c/$newc/}Fall. Ich erwarte sogar in diesem Fall, dass es um einiges schneller sein wird, aber ich mache mir normalerweise keine Sorgen darüber, denn für dieses Zeug verwende ich immer dash- was in jeder Hinsicht um Größenordnungen schneller ist. Und so ist es schwer zu vergleichen.
mikeserv
@terdon - Ich habe es versucht. Aber - auch in bash- tun time (IFS=\"\'`; set -- $var; printf %s "$*")und time (var=${var//\'`/\"/})sowohl Ergebnis in 0.0000sErgebnissen für alle Felder. Mache ich etwas falsch, denkst du? Es sollte einen Backslash vor dem Backquote geben, aber ich weiß nicht, wie man ein Backquote in ein Kommentar-Code-Feld einfügt.
mikeserv