How do you pass arguments from within a rake task and execute a command-line application from Ruby/Rails?
Specifically I'm trying to use pdftk (I can't find a comparable gem) to split some PDFs into individual pages. Args I'd like to pass are within < > below:
$ pdftk <filename.pdf> burst output <filename_%04d.pdf>
In the ruby code for your rake task:
`pdftk #{input_filename} burst output #{output_filename}`
You could also do:
system("pdftk #{input_filename} burst output #{output_filename}")
system() just returns true or false. backticks or %x() returns whatever output the system call generates. Usually backticks are preferred, but if you don't care, then you could use system(). I always use backticks because it's more concise.
More info here: http://rubyquicktips.com/post/5862861056/execute-shell-commands
e.g. as:
filename = 'filename.pdf'
filename_out = 'filename_%04d.pdf'
`pdftk #{filename} burst output #{filename_out}`
or
system("pdftk #{filename} burst output #{filename_out}")
system returns a retrun code, the backtick-version return STDOUT.
If you need stdout and stderr, you may also use Open3.open3:
filename = 'filename.pdf'
filename_out = 'filename_%04d.pdf'
cmd = "pdftk #{filename} burst output #{filename_out}"
require 'open3'
Open3.popen3(cmd){ |stdin, stdout, stderr|
puts "This is STDOUT of #{cmd}:"
puts stdout.read
puts "This is STDERR of #{cmd}:"
puts stderr.read
}
Related
I want my Nim program to write to the console if there is one, and redirect echo to write to a file if there isn't. Is there an equivalent to the Environment.UserInteractive property in .NET which I could use to detect if no console is available and redirect stdout in that case?
It's a combination of using isatty() as suggested by genotrance and the code that you found :)
# stdout_to_file.nim
import terminal, strformat, times
if isatty(stdout): # ./stdout_to_file
echo "This is output to the terminal."
else: # ./stdout_to_file | cat
const
logFileName = "log.txt"
let
# https://github.com/jasonrbriggs/nimwhistle/blob/183c19556d6f11013959d17dfafd43486e1109e5/tests/cgitests.nim#L15
logFile = open(logFileName, fmWrite)
stdout = logFile
echo fmt"This is output to the {logFileName} file."
echo fmt"- Run using nim {NimVersion} on {now()}."
Save above file as stdout_to_file.nim.
On running:
nim c stdout_to_file.nim && ./stdout_to_file | cat
I get this in the created log.txt:
This is output to the log.txt file.
- Run using nim 0.19.9 on 2019-01-23T22:42:27-05:00.
You should be able to use isatty().
Here's an example in Nimble.
Edit:
#tjohnson this is in response to your comment. I don't have enough points to respond to your comment directly or something? Thanks Stack Overflow...
It's hard to say without seeing more of the code.
What version of Nim are you using?
I suspect stdout has been shadowed by a read only symbol.
Are you calling this code inside of a proc and passing stdout as an argument?
like this:
proc foo(stdout: File)
If so, you will need to change it to a var parameter to make the argument writable:
proc test(stdout: var File)
Or use stdout as a global variable instead.
I need to call a command(in a sinatra or rails app) like this:
`command sub`
Some log will be outputed when the command is executing.
I want to see the log displaying continuously in the process.
But I just can get the log string after it's done with:
result = `command sub`
So, is there a way to implement this?
On windows i have the best experience with IO.popen
Here is a sample
require 'logger'
$log = Logger.new( "#{__FILE__}.log", 'monthly' )
#here comes the full command line, here it is a java program
command = %Q{java -jar getscreen.jar #{$userid} #{$password}}
$log.debug command
STDOUT.sync = true
begin
# Note the somewhat strange 2> syntax. This denotes the file descriptor to pipe to a file. By convention, 0 is stdin, 1 is stdout, 2 is stderr.
IO.popen(command+" 2>&1") do |pipe|
pipe.sync = true
while str = pipe.gets #for every line the external program returns
#do somerthing with the capturted line
end
end
rescue => e
$log.error "#{__LINE__}:#{e}"
$log.error e.backtrace
end
There's six ways to do it, but the way you're using isn't the correct one because it waits for the process the return.
Pick one from here:
http://tech.natemurray.com/2007/03/ruby-shell-commands.html
I would use IO#popen3 if I was you.
script.rb:
puts 'hello'
puts 'foo'
main.rb:
puts `jruby script.rb` # receive the expected result
The question:
How can the same be achieved with reading the "script" before execution?
main.rb:
code=File.open('script.rb', 'r').read.gsub('"', '\"')
# puts `jruby -e '#{code}'` # Does not work for relatively big files;
Windows and unicode are the reasons of this question;
Please note that `jruby script.rb' creates a new process which is essential.
Store the modified script in a Tempfile and run that instead of passing the whole contents as an eval argument:
require 'tempfile'
code = IO.read('script.rb').gsub('"', '\"')
begin
tempfile = Tempfile.new 'mytempfile'
f.write code
f.close
puts `jruby '#{f.path}'`
ensure
f.close
f.unlink
end
The reason you’re likely getting an error is either a lack of proper escaping or a limit on the maximum argument length in the shell.
Also, beware that in your original implementation you never close the original file. I’ve fixed that by instead using IO.read.
In the command line, using
$ getconf ARG_MAX
will give the upper limit on how many bytes can be used for the command line argument and environment variables.
#Andrew Marshall's answer is better, but suppose you don't want to use a temp file, and assuming we can use fork in JRuby,
require 'ffi'
module Exec
extend FFI::Library
ffi_lib FFI::Platform::LIBC
attach_function :fork, [], :int
end
code = IO.read('script.rb')
pid = Exec.fork
if 0 == pid
eval code
exit 0
else
Process.waitpid pid
end
use require
main.rb:
require "script.rb"
I've wrote a small function that returns the result of executing a command.
function axsh(cmd)
local fullCmd=cmd:lower()
local f,err=io.popen(fullCmd,"r")
if not f then
return nil,"Could not create the process '"..fullCmd.."' \nError:"..err
end
return f:read("*all")
end
s=axsh("echo hi")
--print all bytes
print(s:byte(1,s:len()))
The output always has a \n at the end no matter what is the command:
104 105 10
Edit: it happens not only for my own binary command line application but also for almost all OS commands: Windows: "dir", "ipconfig", "echo"... Linux: "ls", "pwd", "ls"...
But when I run the command separately (i.e. windows command prompt) there is no trailing line feed. I don't need it, so need to remove the last character before returning the result.
Question: does this line feed always exist in the result of popen()? I can't find any reference to this behavior in the documentation.
No. io.popen just returns whatever string the command produces. You use echo as command, which happens to put a newline after the string ( this is what makes the command prompt appear on the next line, instead of just after the output).
You can test it by using trying this:
s=axsh([[lua -e "io.write([=[hi]=])"]])
return string.byte(s,1,-1)
which does not end the output with a newline.
Say I have want to execute a script or and executable file by printing runtime the output of execution.
When I do:
set log [exec ./executable_file]
puts $log
Then it waits a long time and then prints everything at once. But I want runtime printing. How can I do this?
Not perfect (as it require writing to external file):
set log [exec executable_file | tee log.txt >#stdout]
The output will be displayed immediately, at the same time, saved to 'log.txt'. If you don't care about saving the output:
set log [exec executable_file >#stdout]
Use open "| ..." and asyncronous linewise reading from the returned descriptor, like this:
proc ReadLine fd {
if {[gets $fd line] < 0} {
if {[chan eof $fd]} {
chan close $fd
set ::forever now
return
}
}
puts $line
}
set fd [open "| ./executable_file"]
chan configure $fd -blocking no
chan event $fd readable [list ReadLine $fd]
vwait forever
See this wiki page for more involved examples.
In a real program you will probably already have an event loop running so there would be no need for a vwait specific to reading the output of one command.
Also if you need to collect the output, not just [puts] each line after it has been read, you will pobably need to create a global (usually namespaced) variable, initialize it to "", pass its name as another argument to the callback procedure (ReadLine here) and append the line to that variable's value.
May be you can launch another process in background before your executable, like tail -f logfile.txt, and outputting the results of your executable in this logfile.txt ?