Ruby one-liners: escribir un formato TSV compatible con shell POSIX

Estoy buscando una forma concisa de generar registros TSV que se puedan leer con precisión y sin escapar con un shell POSIX ( IFS='' read -r+ printf %b).

Las reglas de escape minimalistas son:

  • \\ para barra invertida
  • \n para nueva línea
  • \t para la pestaña
  • \r para retorno de carro

Pero se pueden extender al conjunto completo compatibleprintf si existe una manera fácil de hacerlo con Ruby.

El código hasta ahora:

record = [ "\\", "\t", "\n", "\r", "\"" ]

rules = {
  "\\" => "\\\\",
  "\t" => "\\t",
  "\r" => "\\r",
  "\n" => "\\n"
};

regex = /#{ rules.keys.map{|c| Regexp.escape(c)}.join(?|) }/;

puts record.map { |field| field.gsub(regex) {|c| rules[c]} }.join("\t")

Producción:

\\  \t  \n  \r  "

El principal problema es que el código está destinado a frases ingeniosas, por lo que, si es posible, me gustaría reducirlo en gran medida. ¿Alguna idea?

Answer

En Ruby, cada programa siempre se puede escribir como una sola línea, ya que los saltos de línea son opcionales: cada función que puede tener un salto de línea (separador de expresión, introducción de un bloque sintáctico, etc.) también puede ser realizada por un símbolo (p. ej. ;como separador de expresión) o palabra clave (p. ej., thenpara introducir el bloque de consecuencia de una expresión o caseexpresión condicional o dopara introducir el cuerpo de un bucle whileo for, etc.)

Por ejemplo, su código se puede escribir como una sola línea como esta:

record = [ "\\", "\t", "\n", "\r", "\"" ]; rules = { "\\" => "\\\\", "\t" => "\\t", "\r" => "\\r", "\n" => "\\n" }; regex = /#{ rules.keys.map{|c| Regexp.escape(c)}.join(?|) }/; puts record.map { |field| field.gsub(regex) {|c| rules[c]} }.join("\t")

Sin embargo, hay algunas posibles mejoras que podemos hacer en el código.

Utilizar Regexp::unionpara construir regex:

regex = Regexp.union(rules.keys)

Dado que ya se tomó la molestia de construir un reemplazo Hash, ¿por qué no usar la forma de String#gsubque toma un reemplazo Hashcomo argumento ?

puts record.map { |field| field.gsub(regex, rules) }.join("\t")

Tengo una manera:

puts record.map{ |s| s.gsub(/\\|\t|\r|\n/) { |c| c.dump[1,2] } }.join("\t")

String#dumpparece compatible con el shell POSIX printfpara todos los caracteres excepto "(si deja de lado el hecho de que encapsula la cadena entre comillas dobles). Aquí lo estoy usando para escapar de cada carácter objetivo de forma independiente, pero sería más inteligente ejecutarlo en toda la cadena y luego reemplazarlo \"con ".

Actualizar

Este puede funcionar:

puts record.map{|s| s.dump[1..-2].gsub(/\\"/,"\"")}.join("\t")