Module: Kernel
- Defined in:
- lib/qbash.rb
Overview
Execute one bash command.
- Author
-
Yegor Bugayenko ([email protected])
- Copyright
-
Copyright © 2024-2025 Yegor Bugayenko
- License
-
MIT
Instance Method Summary collapse
-
#qbash(cmd, stdin: '', env: {}, log: Loog::NULL, accept: [0], both: false, level: Logger::DEBUG) ⇒ String
Execute a single bash command safely with proper error handling.
Instance Method Details
#qbash(cmd, stdin: '', env: {}, log: Loog::NULL, accept: [0], both: false, level: Logger::DEBUG) ⇒ String
Execute a single bash command safely with proper error handling.
QBash provides a safe way to execute shell commands with proper error handling, logging, and stdin/stdout management. It’s designed to be simple for basic use cases while offering powerful options for advanced scenarios.
Basic Usage
# Execute a command and get its output
year = qbash('date +%Y')
puts "Current year: #{year}"
# Execute a command that might fail
files = qbash('find /tmp -name "*.log"')
Working with Exit Codes
# Get both output and exit code
output, code = qbash('grep "error" /var/log/system.log', both: true)
puts "Command succeeded" if code.zero?
# Accept multiple exit codes as valid
result = qbash('grep "pattern" file.txt', accept: [0, 1])
Providing Input via STDIN
# Pass data to command's stdin
result = qbash('wc -l', stdin: "line 1\nline 2\nline 3")
Environment Variables
# Set environment variables for the command
output = qbash('echo $NAME', env: { 'NAME' => 'Ruby' })
Logging
# Enable detailed logging to stdout
qbash('ls -la', log: $stdout)
# Use custom logger with specific level
logger = Logger.new($stdout)
qbash('make all', log: logger, level: Logger::INFO)
Process Control
# Get control over long-running process
qbash('sleep 30') do |pid|
puts "Process #{pid} is running..."
# Do something while process is running
# Process will be terminated when block exits
end
For command with multiple arguments, you can use Shellwords.escape() to properly escape each argument. Stderr automatically merges with stdout.
Read this <a href=“github.com/yegor256/qbash”>README</a> file for more details.
84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 |
# File 'lib/qbash.rb', line 84 def qbash(cmd, stdin: '', env: {}, log: Loog::NULL, accept: [0], both: false, level: Logger::DEBUG) env.each { |k, v| raise "env[#{k}] is nil" if v.nil? } cmd = cmd.reject { |a| a.nil? || (a.is_a?(String) && a.empty?) }.join(' ') if cmd.is_a?(Array) logit = lambda do |msg| mtd = case level when Logger::DEBUG :debug when Logger::INFO :info when Logger::WARN :warn when Logger::ERROR :error else raise "Unknown log level #{level}" end if log.nil? # nothing to print elsif log.respond_to?(mtd) log.__send__(mtd, msg) else log.print("#{msg}\n") end end logit["+ #{cmd}"] buf = '' e = 1 start = Time.now Open3.popen2e(env, "/bin/bash -c #{Shellwords.escape(cmd)}") do |sin, sout, ctrl| consume = lambda do loop do break if sout.eof? ln = sout.gets next if ln.nil? next if ln.empty? buf += ln ln = "##{ctrl.pid}: #{ln}" logit[ln] rescue IOError => e logit[e.] break end end sin.write(stdin) sin.close if block_given? watch = Thread.new { consume.call } watch.abort_on_exception = true pid = ctrl.pid yield pid sout.close watch.join(0.01) watch.kill if watch.alive? begin Process.getpgid(pid) # should be dead already (raising Errno::ESRCH) Process.kill('TERM', pid) # let's try to kill it begin Process.getpgid(pid) # should be dead now (raising Errno::ESRCH) raise "Process ##{pid} did not terminate after SIGTERM" rescue Errno::ESRCH logit["Process ##{pid} killed with SIGTERM"] end rescue Errno::ESRCH logit["Process ##{pid} exited gracefully"] end else consume.call end e = ctrl.value.to_i if !accept.nil? && !accept.include?(e) raise "The command '#{cmd}' failed with exit code ##{e} in #{start.ago}\n#{buf}" end end return [buf, e] if both buf end |