-1

What's the correct quoting syntax to allow for variable expansion in the following script?

ARGF.each do |l|
  exec cp #{l} #{l}.bk
end

Thanks

Andrew
  • 737
  • 2
  • 8
  • 24
  • `exec` replaces the current process by the given command. Running it in a loop doesn't seem to make much sense as it will never reach the 2nd iteration. – Stefan Feb 13 '23 at 10:23
  • @Stefan argf is by default an array of files. so i am iterating over an array of files. should i not use a loop to loop over them? – Andrew Feb 13 '23 at 10:37
  • Having a loop is fine. `exec` is the problem. It will replace your Ruby process by a `cp` process upon its first invocation, i.e. there will be no Ruby process afterwards that could continue the loop. Maybe [`FileUtils.cp`](https://ruby-doc.org/3.2.0/stdlibs/fileutils/FileUtils.html#method-c-cp) is more appropriate here. – Stefan Feb 13 '23 at 10:51
  • I've also tried nested quotes and it doesn't seem to work. so can we conclude that ruby standard is unable to do variable expansion in conjunction with the shell command? – Andrew Feb 13 '23 at 10:56
  • 1
    In Ruby we call this variable expansion "string interpolation" and it works inside of certain [string literals](https://ruby-doc.org/3.2.0/syntax/literals_rdoc.html#label-String+Literals). Your code doesn't work because there is no string literal in the first place. But even with a string literal (e.g. `exec "cp #{l} #{l}.bk"`) it would only work for the very first item because `exec` effectively terminates your Ruby process. Try `FileUtils.cp(l, "#{l}.bk")` instead and also double-check whether `l` refers to the correct filename. – Stefan Feb 13 '23 at 11:02
  • there are actually multiple strings in the script. one or more is defined at runtime from the command line (hence ARGF) and the other is defined after the word exec. – Andrew Feb 13 '23 at 11:22
  • i see now that what i want is the ARGF.argv array. it stores the filenames and any other input given at runtime from the command line. however replacing the above ARGF with ARGF.argv doesn't recogonize the loop construct. i suppose it must be different with arrays. i am not fluent enough to remember these details so i'll have to go see. – Andrew Feb 13 '23 at 11:24
  • 1
    I’m talking about string _literals_, i.e. something enclosed in double quotes. You might want to re-read my comments and actually try my suggestions. – Stefan Feb 13 '23 at 11:33
  • i tried your suggestions before you offered them. i wouldn't have asked a question if the simple answer worked. thank you though. i appreciate your help. – Andrew Feb 13 '23 at 11:37

2 Answers2

0

In Ruby, in order to evaluate a variable, you simply reference it by name. So, for example, to evaluate a local variable named foo, you simply write

foo

Ruby doesn't have a concept of "variable expansion" like a POSIX shell does. In a POSIX shell, everything is a string, and so you expand variables into strings. Ruby has a much richer and much stronger (dynamic) type system, and a much richer syntax.

So, in order to evaluate the local variable (or more precisely, the block argument binding) l, you would simply write

l

Therefore, your code would look something like this:

ARGF.each do |l|
  exec cp l l.bk
end

Which is parsed like this:

ARGF.each do |l|
  exec(cp(l(l.bk())))
end

Note: in this case, the two references to l actually reference two different things: the first l is a message send, the second l is a local variable reference.

Jörg W Mittag
  • 363,080
  • 75
  • 446
  • 653
  • exec executes in an external context, in plain language that means whatever is after the word exec is as if you typed it in bash and pressed enter. it needs to be a string and that second reference is a filename. ruby HAS variable expansion inside of strings and you reference them like this #{} while inside of double quotes. your code returns an error, as expected because it tries to treat l.bk like a method. – Andrew Feb 13 '23 at 10:30
-1

Use quotes with string interpolation. the problem is that when you run what is verbatim in the ruby docs, ARGF.each do |line|, it does just as says: it loops over lines of the file in the context, but the objective of this script is to copy the filenames-- not access the contents of the files. Therefore when I reference #{l} I am not referencing the filename, but a line within the file.

If you just use ARGF as it is then (i think) it will try to actually read the contents of those files. To reference the names of the files for doing operations like the one above (copy) there are two ways:

ARGF.argv returns an array of the filenames. ARGF.filename returns the filename inside the context of a loop.

Since I'm doing a looping structure I can access the current filename of the loop context with ARGF.filename method.

The correct code looks like this:

ARGF.each do |l|
  exec ("cp #{ARGF.filename} #{ARGF.filename}.bk"
end
Andrew
  • 737
  • 2
  • 8
  • 24
  • _"ARGF.each […] loops over lines of the file"_ – exactly. If your file has 1000 lines, your code would attempt to copy it a thousand times (once for each line). That’s most likely not what you want. Plus, `exec` still terminates the script, see the [docs](https://ruby-doc.org/3.2.1/Kernel.html#method-i-exec) (note the "never get here" part in the example) – Stefan Feb 14 '23 at 05:50