web-dev-qa-db-fra.com

La meilleure façon de s'échapper et d'échapper aux cordes dans Ruby?

Ruby a-t-il une méthode intégrée pour échapper et les chaînes sans échappement? Dans le passé, j'ai utilisé des expressions régulières; cependant, il me semble que Ruby effectue probablement de telles conversions en interne tout le temps. Peut-être que cette fonctionnalité est exposée quelque part.

Jusqu'à présent, je suis venu avec ces fonctions. Ils fonctionnent, mais ils semblent un peu hacky:

def escape(s)
  s.inspect[1..-2]
end

def unescape(s)
  eval %Q{"#{s}"}
end

Y a-t-il une meilleure façon?

25
jwfearn

Ruby 2.5 ajouté String#undump en complément de String#dump :

$ irb
irb(main):001:0> dumped_newline = "\n".dump
=> "\"\\n\""
irb(main):002:0> undumped_newline = dumped_newline.undump
=> "\n"

Avec ça:

def escape(s)
  s.dump[1..-2]
end

def unescape(s)
  "\"#{s}\"".undump
end

$irb
irb(main):001:0> escape("\n \" \\")
=> "\\n \\\" \\\\"
irb(main):002:0> unescape("\\n \\\" \\\\")
=> "\n \" \\"
7
christopheraue

Il existe un tas de méthodes d'échappement, certaines d'entre elles:

# Regexp escapings
>> Regexp.escape('\*?{}.')   
=> \\\*\?\{\}\. 
>> URI.escape("test=100%")
=> "test=100%25"
>> CGI.escape("test=100%")
=> "test%3D100%25"

Donc, cela dépend vraiment du problème que vous devez résoudre. Mais j'éviterais d'utiliser inspect pour s'échapper.

Mise à jour - il y a un vidage, inspectez les utilisations et il semble que c'est ce dont vous avez besoin:

>> "\n\t".dump
=> "\"\\n\\t\""
15

La fonction Caleb était la chose la plus proche de l'inverse de String #inspect que j'ai pu trouver, mais elle contenait deux bogues:

  • \\ n'a pas été géré correctement.
  • \ x .. a conservé la barre oblique inverse.

J'ai corrigé les bugs ci-dessus et voici la version mise à jour:

UNESCAPES = {
    'a' => "\x07", 'b' => "\x08", 't' => "\x09",
    'n' => "\x0a", 'v' => "\x0b", 'f' => "\x0c",
    'r' => "\x0d", 'e' => "\x1b", "\\\\" => "\x5c",
    "\"" => "\x22", "'" => "\x27"
}

def unescape(str)
  # Escape all the things
  str.gsub(/\\(?:([#{UNESCAPES.keys.join}])|u([\da-fA-F]{4}))|\\0?x([\da-fA-F]{2})/) {
    if $1
      if $1 == '\\' then '\\' else UNESCAPES[$1] end
    elsif $2 # escape \u0000 unicode
      ["#$2".hex].pack('U*')
    elsif $3 # escape \0xff or \xff
      [$3].pack('H2')
    end
  }
end

# To test it
while true
    line = STDIN.gets
    puts unescape(line)
end
15
antirez

Mise à jour : je ne suis plus d'accord avec ma propre réponse, mais je préférerais ne pas la supprimer car je soupçonne que d'autres peuvent emprunter cette mauvaise voie, et il y a déjà eu beaucoup de discussions sur cette réponse et ses alternatives, donc je pense que cela contribue toujours à la conversation, mais veuillez ne pas utiliser cette réponse dans le vrai code.

Si vous ne souhaitez pas utiliser eval, mais souhaitez utiliser le module YAML, vous pouvez l'utiliser à la place:

require 'yaml'

def unescape(s)
  YAML.load(%Q(---\n"#{s}"\n))
end

L'avantage de YAML sur eval est qu'il est probablement plus sûr. cane interdit toute utilisation de eval. J'ai vu des recommandations pour utiliser $SAFE avec eval, mais qui n'est pas disponible via JRuby actuellement.

Pour ce que cela vaut, Python a un support natif pour barres obliques inverses .

13
b4hand

Ruby inspect peut vous aider:

    "a\nb".inspect
=> "\"a\\nb\""

Normalement, si nous imprimons une chaîne avec un saut de ligne intégré, nous obtenons:

puts "a\nb"
a
b

Si nous imprimons la version inspectée:

puts "a\nb".inspect
"a\nb"

Assignez la version inspectée à une variable et vous aurez la version d'échappement de la chaîne.

Pour annuler l'échappement, eval la chaîne:

puts eval("a\nb".inspect)
a
b

Je n'aime pas vraiment le faire de cette façon. C'est plus une curiosité que quelque chose que je ferais en pratique.

11
the Tin Man

YAML's ::unescape ne semble pas échapper aux guillemets, par exemple ' et ". Je suppose que c'est par conception, mais cela me rend triste.

Vous ne voulez certainement pas utiliser eval sur des données arbitraires ou fournies par le client.

C'est ce que j'utilise. Gère tout ce que j'ai vu et n'introduit aucune dépendance.

UNESCAPES = {
    'a' => "\x07", 'b' => "\x08", 't' => "\x09",
    'n' => "\x0a", 'v' => "\x0b", 'f' => "\x0c",
    'r' => "\x0d", 'e' => "\x1b", "\\\\" => "\x5c",
    "\"" => "\x22", "'" => "\x27"
}

def unescape(str)
  # Escape all the things
  str.gsub(/\\(?:([#{UNESCAPES.keys.join}])|u([\da-fA-F]{4}))|\\0?x([\da-fA-F]{2})/) {
    if $1
      if $1 == '\\' then '\\' else UNESCAPES[$1] end
    elsif $2 # escape \u0000 unicode
      ["#$2".hex].pack('U*')
    elsif $3 # escape \0xff or \xff
      [$3].pack('H2')
    end
  }
end
10
Caleb Fenton
5
MattyB