caminho mais curto para substituir caracteres em uma variável

17

Existem muitas maneiras de substituir caracteres em uma variável.

O caminho mais curto que descobri é tr até agora:

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

Existe um caminho mais rápido? E isso é seguro para citações como ' , " e 'próprio?

    
por rubo77 15.10.2014 / 11:54

4 respostas

19

Vamos ver. O mais curto que eu puder apresentar é um ajuste da sua solução tr :

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

Outras alternativas incluem a substituição de variável já mencionada, que pode ser menor do que a mostrada até agora:

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

E sed , é claro, mas isso é mais longo em termos de caracteres:

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

Não sei se você quer dizer o menor tempo ou o tempo gasto. Em termos de comprimento, esses dois são tão curtos quanto possível (ou como eu posso obtê-lo de qualquer maneira) quando se trata de remover esses caracteres específicos. Então, qual é o mais rápido? Eu testei definindo a variável OUTPUT para o que você tinha no seu exemplo, mas repeti várias dúzias de vezes:

$ 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

Como você pode ver, o tr é claramente o mais rápido, seguido de perto por sed . Além disso, parece que usar echo é realmente um pouco mais rápido do que usar <<< :

$ 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

Como a diferença é pequena, eu corri os testes acima 10 vezes para cada um dos dois e acontece que o mais rápido é de fato o que você tinha que começar:

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

No entanto, isso muda quando você leva em conta a sobrecarga de atribuir a uma variável, aqui, usando tr é um pouco mais lento que a substituição simples:

$ 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

Então, em conclusão, quando você simplesmente deseja visualizar os resultados, use tr , mas se quiser reatribuir a uma variável, usar os recursos de manipulação de string do shell é mais rápido, pois eles evitam a sobrecarga de execução de uma subpela separada.

    
por 15.10.2014 / 12:29
15

Você pode usar a substituição de variável :

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

Use essa sintaxe: ${parameter//pattern/string} para substituir todas as ocorrências do padrão pela string.

$ echo "${OUTPUT//\'/x}"
axb"c'd
$ echo "${OUTPUT//\"/x}"
a'bxc'd
$ echo "${OUTPUT//\'/x}"
a'b"cxd
$ echo "${OUTPUT//[\'\"\']/x}"
axbxcxd
    
por 15.10.2014 / 12:03
11

No bash ou zsh, é:

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

Observe que ${VAR//PATTERN/} remove todas as instâncias do padrão. Para obter mais informações expansão do parâmetro bash

Essa solução deve ser mais rápida para strings curtas, pois não envolve a execução de nenhum programa externo. No entanto, para strings muito longas, o oposto é verdadeiro - é melhor usar uma ferramenta dedicada para operações de texto, por exemplo:

$ 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
    
por 15.10.2014 / 12:09
6

Se, por acaso, você está apenas tentando lidar com as cotações para reutilizar o shell, então você pode fazer isso sem removê-las, e também é muito simples:

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

Essa função shell cita qualquer array arg que você distribui e incrementa sua saída por argumento iterável.

Aqui está com alguns argumentos:

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

OUTPUT

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

Essa saída é de dash , que normalmente apresenta cotações únicas com cotação segura, como '"'"' . bash faria '\'' .

Substituir uma seleção de bytes únicos, não-whitespace, não-nulos por outro byte único pode provavelmente ser feito mais rápido em qualquer shell POSIX com $IFS e $* .

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

OUTPUT

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

Há apenas printf para que você possa ver, mas é claro, se eu tivesse feito:

var="$*"

... em vez do valor de printf command $var , seria o que você vê na saída.

Quando eu set -f instruo o shell não para glob - caso a string contenha caracteres que possam ser interpretados como padrões glob. Eu faço isso porque o analisador de shells expande os padrões de glob após executar a divisão de campo nas variáveis. globbing pode ser reativado como set +f . Em geral - em scripts - acho útil definir meu estrondo como:

#!/usr/bin/sh -f

E, em seguida, para ativar explicitamente a globbing com set +f em qualquer linha que eu queira.

A divisão de campo ocorre com base nos caracteres em $IFS .

Existem dois tipos de valores $IFS - $IFS whitespace e $IFS non-whitespace. $IFS whitespace (espaço, tabulação, nova linha) campos delimitados são especificados para serem eliminados por sequência em um único campo (ou nenhum em todos eles se não precederem outra coisa) - so ...

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

Mas todos os outros são especificados para avaliar um único campo por ocorrência - eles não são truncados.

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

Todas as expansões de variáveis são, por padrão, $IFS de matrizes de dados delimitadas - elas se dividem em campos separados de acordo com $IFS . Quando você " -quota você sobrescreve aquela propriedade de array e avalia-a como uma única string.

Então quando eu faço ...

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

Estou configurando a matriz de argumentos do shell para os muitos campos delimitados $IFS gerados pela expansão $var . Quando é expandido, seus valores constituintes para os caracteres contidos em $IFS são perdidos - eles são apenas separadores de campo agora - eles são "$*"NUL .

$IFS - como outras expansões variáveis com aspas duplas - também substitui as qualidades de divisão de campo de $IFS . Mas, além disso , ele substitui o primeiro byte em "$@" por cada campo delimitado em " . Então, porque $IFS foi o valor primeiro em " todos os delimitadores subseqüentes se tornaram "$*" em " . E o $IFS não precisa estar em $IFS quando você o divide também. Você pode alterar set -- $args após "$*" para outro valor completo e seu primeiro byte novo será exibido para os delimitadores de campo em %code% . Além do mais, você pode remover todos os vestígios deles como:

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

OUTPUT

some crazy string here
    
por 15.10.2014 / 21:05