Hi, today I am writing to you because a lot of time, I am facing a problem to integrate properly some Linux call with Process.run.
I think as well it’s the origin of some bugs in my project.
Today, I was coding just a small script to perform a migration for some files. I was just testing a function to make sure it work properly.
My function:
def repack(filename)
process = Process.run("tar",args: ["cvfJ",filename,"*"],shell: true,error: :inherit,chdir: tempDirectory)
puts "tar cvfJ #{filename} * (#{tempDirectory})"
end
Unfortunately, it doesn’t work, and have this strange error:
zohran@alienware-m17-r3 ~/Downloads $ crystal Repack.cr
tar: *: Cannot stat: No such file or directory
tar: Exiting with failure status due to previous errors
tar cvfJ Acl.tar.xz * (/home/zohran/Work/temp/)
The very strange thing is, if I copy past this command to execute it in a shell, it just work …
What is wrong ? I feel like the translation to crystal with some characters don’t work very well, like special characters like the star *
I think what’s going on is it works in your shell because the * is a glob that expands to all files in your current dir, which is essentially passing a list of filenames to tar. However, Crystal isn’t able to do that same sort of glob expansion by default, so it’s passing a literal *. So I think you just want to pass "." instead of "*" to let tar itself find all the files and not your shell and should be good to go.
So in this case, being on shell: true mode, you actually don’t want to provide your args via the args array, but instead as part of the command itself. This would then run it thru /bin/sh which does the proper expanding of the glob. Probably wouldn’t be a bad idea passing the filename to Process.quote if its user supplied as well.
So if I wish to use args:, I need to set shell: to false right ?
Because if I set it to false:
def repack(filename)
process = Process.run("tar",args: ["cvfJ",filename,"*"],error: :inherit,shell: false,chdir: tempDirectory)
puts "tar cvfJ #{filename} * (#{tempDirectory})"
end
Result:
zohran@alienware-m17-r3 ~/Downloads $ crystal Repack.cr
tar: *: Cannot stat: No such file or directory
tar: Exiting with failure status due to previous errors
tar cvfJ Acl.tar.xz * (/home/zohran/Work/temp/)
I tried. Set shell: to true ou false doesn’t change the problem. So I will try without args. But it’s a bit annoying for me, because if it’s like that, I will probably need to change a lot of parts in my code
If you want shell expansion, you need to use the shell. A command goes through the shell if the entire command is provided in the first argument and shell: true. That’s how it works.
If you want to use an argument array, you may try to expand the array into the command string (command: "tar #{[cvfJ, filename, "*"].join(" ")}", shell: true).
If you don’t want to use the shell, you can pass the filenames directly into the program instead of letting the shell expand them: args: ["cvfJ", filename, Dir.glob("*").join(" ")], shell: false.