il modo più breve per sostituire i caratteri in una variabile


18

Esistono molti modi per sostituire i caratteri in una variabile.

Il modo più breve che ho scoperto è trfinora:

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

C'è un modo più veloce? Ed è questa citazione di sicurezza per le citazioni come ', "e `se stessa?


Penso che tu possa continuare a usare tr. Il PE di BASH è buono, ma in questo caso tr è molto più veloce. ad es. echo "$OUTPUT" | tr -dc '[[:alpha:]]' dal momento che si desidera avere solo caratteri alfanumerici
Valentin Bajrami,

2
Dato che ti interessa essere esperto di quotazioni: cita sempre le tue variabili! echo "$OUTPUT". O meglio: printf "%s\n" "$OUTPUT". (Cosa succede quando OUTPUT="-n"?)
musiphil,

Potresti anche prendere in considerazione la navigazione in codegolf , in particolare i consigli di bash .
hoosierEE

Risposte:


22

Vediamo. Il più breve che posso inventare è una modifica della tua trsoluzione:

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

Altre alternative includono la sostituzione delle variabili già menzionata che può essere più breve di quanto mostrato finora:

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

E sedovviamente questo è più lungo in termini di personaggi:

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

Non sono sicuro se intendi la lunghezza più breve o in termini di tempo impiegato. In termini di lunghezza, questi due sono i più corti possibile (o comunque riesco a ottenerlo) quando si tratta di rimuovere quei personaggi specifici. Quindi, qual è il più veloce? Ho testato impostando la OUTPUTvariabile su quello che avevi nel tuo esempio, ma ho ripetuto diverse decine di volte:

$ 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

Come puoi vedere, trè chiaramente il più veloce, seguito da vicino sed. Inoltre, sembra che l'utilizzo echosia in realtà leggermente più veloce dell'uso <<<:

$ 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

Poiché la differenza è minuscola, ho eseguito i test di cui sopra 10 volte per ciascuno dei due e si scopre che il più veloce è effettivamente quello con cui hai dovuto iniziare:

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

Tuttavia, questo cambia quando si tiene conto del sovraccarico di assegnazione a una variabile, qui, l'utilizzo trè leggermente più lento della semplice sostituzione:

$ 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

Quindi, in conclusione, quando si desidera semplicemente visualizzare i risultati, utilizzare trma se si desidera riassegnare a una variabile, l'uso delle funzionalità di manipolazione delle stringhe della shell è più veloce poiché evitano il sovraccarico dell'esecuzione di una subshell separata.


4
Dato che l'OP è interessato a riportare il valore modificato in OUTPUT, dovrai tenere conto dell'overhead della sotto-shell di sostituzione dei comandi coinvolto in tre sedsoluzioni
iruvar,

@ 1_CR sì, ma dato che sarà il caso di qualunque metodo utilizzi, ho pensato che fosse irrilevante.
terdon

1
Non del tutto, OUTPUT="${OUTPUT//[`\"\']/}" non implica la sostituzione dei comandi
iruvar,

@ 1_CR ah, vedo, sì, hai perfettamente ragione e questo cambia il risultato. Grazie, risposta modificata.
terdon

2
I metodi che comportano una sostituzione di comando hanno il rovescio della medaglia in qualche modo rovinare la stringa. (È possibile evitarlo, ma a spese di rendere il comando significativamente più complesso.) In particolare, la sostituzione del comando rimuove le nuove righe finali.
Gilles 'SO- smetti di essere cattivo'

15

È possibile utilizzare la sostituzione variabile :

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

Usa quella sintassi: ${parameter//pattern/string}per sostituire tutte le occorrenze del pattern con la stringa.

$ 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
caos il

Non è corretto nominare l'espansione "espansione variabile". Si chiama "espansione dei parametri".
gena2x,

@ gena2x - Non capisco cosa significhi qui il tuo commento?
slm

12

In bash o zsh è:

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

Si noti che ${VAR//PATTERN/}rimuove tutte le istanze del modello. Per ulteriori informazioni sull'espansione dei parametri bash

Questa soluzione dovrebbe essere la più veloce per stringhe brevi perché non comporta l'esecuzione di alcun programma esterno. Tuttavia per stringhe molto lunghe è vero il contrario: è meglio usare uno strumento dedicato per le operazioni di testo, ad esempio:

$ 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
In effetti, trè più veloce. Regex e globs sono costosi, e anche se non esiste un programma esterno qui, bash sarà sempre più lento di qualcosa del genere tr.
terdon

Ciò dipende fortemente dai dati di input e dall'implementazione di regexp. Nella tua risposta hai preso alcuni specifici set di dati di grandi dimensioni, ma il set di dati potrebbe essere piccolo. O diverso. Inoltre, non misuri il tempo di regexp ma il tempo di eco, quindi non posso essere sicuro che il tuo confronto sia davvero corretto.
gena2x,

Punti buoni. Tuttavia, non è possibile presentare reclami sulla velocità senza test. In effetti, quando si assegna a una variabile questo sembra più veloce, ma quando si stampa sullo schermo trvince (vedi la mia risposta). Concordo sul fatto che dipenderà da molti fattori, ma è esattamente per questo che non si può dire quale si vince senza effettivamente provarlo.
terdon

6

Se, per caso, stai solo cercando di gestire le virgolette per riutilizzare la shell, puoi farlo senza rimuoverle, ed è anche semplicissimo:

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

Quella shell di funzioni cita qualsiasi array arg che viene passato e ne incrementa l'output per argomento iterabile.

Eccolo con alcuni argomenti:

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

PRODUZIONE

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

Quell'output proviene dashda quello tipicamente sicuro quotazioni a virgoletta singola come '"'"'. bashfarebbe '\''.

La sostituzione di una selezione di byte singoli, non bianchi, non nulli con un altro singolo byte può essere probabilmente eseguita più rapidamente in qualsiasi shell POSIX con $IFSe $*.

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

PRODUZIONE

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

Eccolo printflì, così puoi vederlo, ma ovviamente se avessi fatto:

var="$*"

... piuttosto che il valore del printfcomando $varsarebbe quello che vedi nell'output lì.

Quando set -fistruisco la shell di non glob, nel caso in cui la stringa contenga caratteri che potrebbero essere interpretati come modelli glob. Lo faccio perché il parser di shell espande i modelli glob dopo aver eseguito la divisione dei campi sulle variabili. il globbing può essere riattivato come set +f. In generale, negli script, trovo utile impostare il mio botto come:

#!/usr/bin/sh -f

E poi per abilitare esplicitamente globbing con set +fsu qualsiasi linea che potrebbe desiderare.

La suddivisione dei campi avviene in base ai caratteri in $IFS.

Esistono due tipi di $IFSvalori: $IFSspazi bianchi e $IFSnon bianchi. $IFSi campi delimitati da spazi bianchi (spazio, tabulazione, nuova riga) vengono specificati per elidare in sequenza in un singolo campo (o nessuno se non precedono qualcos'altro) - quindi ...

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

Ma tutti gli altri sono specificati per valutare un singolo campo per occorrenza - non sono troncati.

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

Tutte le espansioni variabili sono, per impostazione predefinita, $IFSarray di dati delimitati - suddivisi in campi separati in base a $IFS. Quando ne "citate una, sovrascrivete quella proprietà dell'array e la valutate come una singola stringa.

Quindi quando lo faccio ...

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

Sto impostando l'array di argomenti della shell sui molti $IFScampi delimitati generati $vardall'espansione di. Quando viene espanso, i suoi valori costitutivi per i caratteri contenuti $IFSvengono persi - ora sono solo separatori di campo - lo sono \0NUL.

"$*"- come altre espansioni variabili tra virgolette doppie - sostituisce anche le qualità di divisione del campo di $IFS. Ma, in aggiunta , esso sostituisce il primo byte $IFS per ogni campo delimitato in "$@". Quindi perché è "stato il primo valore in $IFS tutti i delimitatori successivi "in "$*". E "non è nemmeno necessario che $IFStu lo divida. Si potrebbe modificare $IFS dopo set -- $args un altro valore del tutto e il suo nuovo primo byte potrebbe quindi presentarsi per i delimitatori di campo in "$*". Inoltre, è possibile rimuovere tutte le tracce del tutto come:

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

PRODUZIONE

some crazy string here

Molto bello, +1. Mi chiedo se sia davvero più veloce. Potresti aggiungere alcuni test di temporizzazione confrontandolo con gli approcci nella mia risposta? Mi aspetto che il tuo sia più veloce ma mi piacerebbe vedere.
terdon

@terdon - dipende dalla shell. E ' quasi sicuramente più veloce rispetto tra qualsiasi shell, ma la differenza è incerto in bashper il ${var//$c/$newc/}caso. Mi aspetto che anche in quel caso sarà più veloce di qualche margine, ma di solito non mi preoccupo perché per questa roba che uso sempre dash- che è più veloce per ordini di grandezza in generale sotto tutti gli aspetti. E quindi è difficile confrontare.
Mikeserv,

@terdon - ci ho provato. Ma - anche in bash- fare time (IFS=\"\'`; set -- $var; printf %s "$*")e time (var=${var//\'`/\"/})sia risultato in 0.0000srisultati per tutti i campi. Sto facendo qualcosa di sbagliato, pensi? Dovrebbe esserci una barra rovesciata prima del backquote lassù ma non so come mettere un backquote in un campo di codice di commento.
Mikeserv,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.