Especifique un valor nulo en los argumentos de la línea de comando

Tengo una herramienta de línea de comandos que genera un documento json plano. Imagina esto:

$ ruby gen-json.rb --foo bar --baz qux
{
    "foo": "bar"
    "baz": "qux"
}

Lo que quiero es que esto funcione:

$ ruby gen-json.rb --foo $'\0' --baz qux
{
    "foo": null,
    "baz": "qux"
}

En lugar de nulo, obtengo una cadena vacía. Para simplificar el problema aún más considere esto:

$ cat nil-args.rb
puts "argv[0] is nil" if ARGV[0].nil?
puts "argv[0] is an empty string" if ARGV[0] == ""
puts "argv[1] is nil" if ARGV[1].nil?

Quiero ejecutarlo así y obtener este resultado:

$ ruby nil-args.rb $'\0' foo
argv[0] is nil

Pero en cambio consigo

argv[0] is an empty string

Sospecho que esto es (posiblemente) un error en el intérprete de Ruby. Está tratando argv[0] como una cadena C que termina en nulo.

preguntado el 03 de mayo de 12 a las 21:05

¿Porque una cadena con el número de caracteres 0 en ASCII es una cadena vacía? -

El código ASCII 0 es NUL, que C usa para terminar arreglos de cadenas. Entonces no puedes tener nul en una c-string. Estoy sugiriendo que el intérprete de Ruby debería leer una cadena y si solo es NUL, entonces establezca la var en cero. Eso probablemente rompa un montón de cosas porque el tipo de ARGV[0] ya no es String. Así que no creo que lo que quiero sea posible. -

+1, pero busque en Google la frase "seleccionar no está roto" acerca de que es un error. -

Una posible solución es que usted puede pasar caracteres NUL a través de un tubo. Sin embargo, no puede almacenar NUL en una variable. -

4 Respuestas

Los argumentos de la línea de comando son siempre instrumentos de cuerda. Deberá usar un centinela para indicar los argumentos que desea tratar de otra manera.

contestado el 03 de mayo de 12 a las 21:05

¿No podría ser una cadena que contenga un carácter, el carácter NUL? - jaybuff

El problema es que NUL es invisible en el nivel C, por lo que no es visible en Ruby. - Ignacio Vázquez-Abrams

Estoy bastante seguro de que literalmente no se puede hacer lo que te propones. Es una limitación fundamental del shell que está utilizando. Solo puede pasar argumentos de cadena a un script.

Ya se mencionó en un comentario, pero el resultado que obtiene con el método \0 que probó tiene mucho sentido. El terminador nulo técnicamente es una cadena vacía.

Considere también que acceder a cualquier elemento de una matriz que aún no se haya definido siempre será nulo.

a = [1, 2, 3]
a[10].nil?
#=> true

Sin embargo, una posible solución sería que su programa funcione así:

$ ruby gen-json.rb --foo --baz qux
{
    "foo": null,
    "baz": "qux"
}

Entonces, cuando tiene un argumento de doble signo menos seguido de otro argumento de doble signo menos, infiere que el primero era nulo. Sin embargo, deberá escribir su propio analizador de opciones de línea de comando para lograr esto.

Aquí hay un script de ejemplo muy simple que parece funcionar (pero probablemente tenga casos extremos y otros problemas):

require 'pp'

json = {}

ARGV.each_cons(2) do |key, value|
  next unless key.start_with? '--'
  json[key] = value.start_with?('--') ? nil : value
end

pp json

¿Funcionaría eso para sus propósitos? :)

contestado el 04 de mayo de 12 a las 01:05

Ruby trata TODO excepto nil y false as true. Por lo tanto, cualquier cadena vacía se evaluará como verdadera (así como 0 or 0.0).

Puede forzar el comportamiento nulo teniendo algo como esto:

ARGV.map! do |arg|
  if arg.empty?
    nil
  else
    arg
  end
end

de esta manera, cualquier cadena vacía se transformará en una referencia a nil.

contestado el 03 de mayo de 12 a las 21:05

Quiero poder diferenciar entre cero y una cadena vacía. - jaybuff

Entonces no veo donde reside tu problema. Ya estás, por defecto, diferenciando entre nil y una cadena vacía (de manera que cuando pasa una cadena vacía como argumento, NO se evaluará como nil), pero lo que aparentemente quieres NO es hacerlo. Exactamente cuando ¿Quieres que se asigne una cadena vacía? nil y cuando ¿Quiere que una cadena vacía se trate como es? - André Medeiros

Una cadena (rubí) que contiene NUL es diferente a una cadena vacía: jaybuff

Vas a tener que decidir sobre un carácter especial para que signifique cero. Yo sugeriría "\0" (las comillas dobles garantizan la cadena literal barra invertida cero se pasa). A continuación, puede utilizar un convertidor de tipo especial en OptionParser:

require 'optparse'
require 'json'

values = {}
parser = OptionParser.new do |opts|
  opts.on("--foo VAL",:nulldetect) { |val| values[:foo] = val }
  opts.on("--bar VAL",:nulldetect) { |val| values[:foo] = val }

  opts.accept(:nulldetect) do |string|
    if string == '\0'
      nil
    else
      string
    end
  end
end

parser.parse!

puts values.to_json

Obviamente, esto requiere que seas explícito acerca de las banderas que aceptas, pero si quieres aceptar cualquier bandera, ciertamente puedes bloquear manualmente el if declaraciones para verificar "\0"

Como nota al margen, puede hacer que las banderas sean opcionales, pero obtiene la cadena vacía pasada al bloque, por ejemplo

opts.on("--foo [VAL]") do |val|
  # if --foo is passed w/out args, val is empty string
end

Entonces, aún necesitarías un suplente

contestado el 09 de mayo de 12 a las 13:05

No es la respuesta que estás buscando? Examinar otras preguntas etiquetadas or haz tu propia pregunta.