1# 2# shell.rb - 3# $Release Version: 0.7 $ 4# $Revision: 1.9 $ 5# by Keiju ISHITSUKA(keiju@ruby-lang.org) 6# 7# -- 8# 9# 10# 11 12require "e2mmap" 13 14require "thread" unless defined?(Mutex) 15 16require "forwardable" 17 18require "shell/error" 19require "shell/command-processor" 20require "shell/process-controller" 21 22# Shell implements an idiomatic Ruby interface for common UNIX shell commands. 23# 24# It provides users the ability to execute commands with filters and pipes, 25# like +sh+/+csh+ by using native facilities of Ruby. 26# 27# == Examples 28# 29# === Temp file creation 30# 31# In this example we will create three +tmpFile+'s in three different folders 32# under the +/tmp+ directory. 33# 34# sh = Shell.cd("/tmp") # Change to the /tmp directory 35# sh.mkdir "shell-test-1" unless sh.exists?("shell-test-1") 36# # make the 'shell-test-1' directory if it doesn't already exist 37# sh.cd("shell-test-1") # Change to the /tmp/shell-test-1 directory 38# for dir in ["dir1", "dir3", "dir5"] 39# if !sh.exists?(dir) 40# sh.mkdir dir # make dir if it doesnt' already exist 41# sh.cd(dir) do 42# # change to the `dir` directory 43# f = sh.open("tmpFile", "w") # open a new file in write mode 44# f.print "TEST\n" # write to the file 45# f.close # close the file handler 46# end 47# print sh.pwd # output the process working directory 48# end 49# end 50# 51# === Temp file creationg with self 52# 53# This example is identical to the first, except we're using 54# CommandProcessor#transact. 55# 56# CommandProcessor#transact executes the given block against self, in this case 57# +sh+; our Shell object. Within the block we can substitute +sh.cd+ to +cd+, 58# because the scope within the block uses +sh+ already. 59# 60# sh = Shell.cd("/tmp") 61# sh.transact do 62# mkdir "shell-test-1" unless exists?("shell-test-1") 63# cd("shell-test-1") 64# for dir in ["dir1", "dir3", "dir5"] 65# if !exists?(dir) 66# mkdir dir 67# cd(dir) do 68# f = open("tmpFile", "w") 69# f.print "TEST\n" 70# f.close 71# end 72# print pwd 73# end 74# end 75# end 76# 77# === Pipe /etc/printcap into a file 78# 79# In this example we will read the operating system file +/etc/printcap+, 80# generated by +cupsd+, and then output it to a new file relative to the +pwd+ 81# of +sh+. 82# 83# sh = Shell.new 84# sh.cat("/etc/printcap") | sh.tee("tee1") > "tee2" 85# (sh.cat < "/etc/printcap") | sh.tee("tee11") > "tee12" 86# sh.cat("/etc/printcap") | sh.tee("tee1") >> "tee2" 87# (sh.cat < "/etc/printcap") | sh.tee("tee11") >> "tee12" 88# 89class Shell 90 @RCS_ID='-$Id: shell.rb,v 1.9 2002/03/04 12:01:10 keiju Exp keiju $-' 91 92 include Error 93 extend Exception2MessageMapper 94 95# @cascade = true 96 # debug: true -> normal debug 97 # debug: 1 -> eval definition debug 98 # debug: 2 -> detail inspect debug 99 @debug = false 100 @verbose = true 101 102 @debug_display_process_id = false 103 @debug_display_thread_id = true 104 @debug_output_mutex = Mutex.new 105 106 class << Shell 107 extend Forwardable 108 109 attr_accessor :cascade, :debug, :verbose 110 111# alias cascade? cascade 112 alias debug? debug 113 alias verbose? verbose 114 @verbose = true 115 116 def debug=(val) 117 @debug = val 118 @verbose = val if val 119 end 120 121 122 # call-seq: 123 # Shell.cd(path) 124 # 125 # Creates a new Shell instance with the current working directory 126 # set to +path+. 127 def cd(path) 128 new(path) 129 end 130 131 # Returns the directories in the current shell's PATH environment variable 132 # as an array of directory names. This sets the system_path for all 133 # instances of Shell. 134 # 135 # Example: If in your current shell, you did: 136 # 137 # $ echo $PATH 138 # /usr/bin:/bin:/usr/local/bin 139 # 140 # Running this method in the above shell would then return: 141 # 142 # ["/usr/bin", "/bin", "/usr/local/bin"] 143 # 144 def default_system_path 145 if @default_system_path 146 @default_system_path 147 else 148 ENV["PATH"].split(":") 149 end 150 end 151 152 # Sets the system_path that new instances of Shell should have as their 153 # initial system_path. 154 # 155 # +path+ should be an array of directory name strings. 156 def default_system_path=(path) 157 @default_system_path = path 158 end 159 160 def default_record_separator 161 if @default_record_separator 162 @default_record_separator 163 else 164 $/ 165 end 166 end 167 168 def default_record_separator=(rs) 169 @default_record_separator = rs 170 end 171 172 # os resource mutex 173 mutex_methods = ["unlock", "lock", "locked?", "synchronize", "try_lock", "exclusive_unlock"] 174 for m in mutex_methods 175 def_delegator("@debug_output_mutex", m, "debug_output_"+m.to_s) 176 end 177 178 end 179 180 # call-seq: 181 # Shell.new(pwd, umask) -> obj 182 # 183 # Creates a Shell object which current directory is set to the process 184 # current directory, unless otherwise specified by the +pwd+ argument. 185 def initialize(pwd = Dir.pwd, umask = nil) 186 @cwd = File.expand_path(pwd) 187 @dir_stack = [] 188 @umask = umask 189 190 @system_path = Shell.default_system_path 191 @record_separator = Shell.default_record_separator 192 193 @command_processor = CommandProcessor.new(self) 194 @process_controller = ProcessController.new(self) 195 196 @verbose = Shell.verbose 197 @debug = Shell.debug 198 end 199 200 # Returns the command search path in an array 201 attr_reader :system_path 202 203 # Sets the system path (the Shell instance's PATH environment variable). 204 # 205 # +path+ should be an array of directory name strings. 206 def system_path=(path) 207 @system_path = path 208 rehash 209 end 210 211 212 # Returns the umask 213 attr_accessor :umask 214 attr_accessor :record_separator 215 attr_accessor :verbose, :debug 216 217 def debug=(val) 218 @debug = val 219 @verbose = val if val 220 end 221 222 alias verbose? verbose 223 alias debug? debug 224 225 attr_reader :command_processor 226 attr_reader :process_controller 227 228 def expand_path(path) 229 File.expand_path(path, @cwd) 230 end 231 232 # Most Shell commands are defined via CommandProcessor 233 234 # 235 # Dir related methods 236 # 237 # Shell#cwd/dir/getwd/pwd 238 # Shell#chdir/cd 239 # Shell#pushdir/pushd 240 # Shell#popdir/popd 241 # Shell#mkdir 242 # Shell#rmdir 243 244 # Returns the current working directory. 245 attr_reader :cwd 246 alias dir cwd 247 alias getwd cwd 248 alias pwd cwd 249 250 attr_reader :dir_stack 251 alias dirs dir_stack 252 253 # call-seq: 254 # Shell.chdir(path) 255 # 256 # Creates a Shell object which current directory is set to +path+. 257 # 258 # If a block is given, it restores the current directory when the block ends. 259 # 260 # If called as iterator, it restores the current directory when the 261 # block ends. 262 def chdir(path = nil, verbose = @verbose) 263 check_point 264 265 if iterator? 266 notify("chdir(with block) #{path}") if verbose 267 cwd_old = @cwd 268 begin 269 chdir(path, nil) 270 yield 271 ensure 272 chdir(cwd_old, nil) 273 end 274 else 275 notify("chdir #{path}") if verbose 276 path = "~" unless path 277 @cwd = expand_path(path) 278 notify "current dir: #{@cwd}" 279 rehash 280 Void.new(self) 281 end 282 end 283 alias cd chdir 284 285 # call-seq: 286 # pushdir(path) 287 # pushdir(path) { &block } 288 # 289 # Pushes the current directory to the directory stack, changing the current 290 # directory to +path+. 291 # 292 # If +path+ is omitted, it exchanges its current directory and the top of its 293 # directory stack. 294 # 295 # If a block is given, it restores the current directory when the block ends. 296 def pushdir(path = nil, verbose = @verbose) 297 check_point 298 299 if iterator? 300 notify("pushdir(with block) #{path}") if verbose 301 pushdir(path, nil) 302 begin 303 yield 304 ensure 305 popdir 306 end 307 elsif path 308 notify("pushdir #{path}") if verbose 309 @dir_stack.push @cwd 310 chdir(path, nil) 311 notify "dir stack: [#{@dir_stack.join ', '}]" 312 self 313 else 314 notify("pushdir") if verbose 315 if pop = @dir_stack.pop 316 @dir_stack.push @cwd 317 chdir pop 318 notify "dir stack: [#{@dir_stack.join ', '}]" 319 self 320 else 321 Shell.Fail DirStackEmpty 322 end 323 end 324 Void.new(self) 325 end 326 alias pushd pushdir 327 328 # Pops a directory from the directory stack, and sets the current directory 329 # to it. 330 def popdir 331 check_point 332 333 notify("popdir") 334 if pop = @dir_stack.pop 335 chdir pop 336 notify "dir stack: [#{@dir_stack.join ', '}]" 337 self 338 else 339 Shell.Fail DirStackEmpty 340 end 341 Void.new(self) 342 end 343 alias popd popdir 344 345 # Returns a list of scheduled jobs. 346 def jobs 347 @process_controller.jobs 348 end 349 350 # call-seq: 351 # kill(signal, job) 352 # 353 # Sends the given +signal+ to the given +job+ 354 def kill(sig, command) 355 @process_controller.kill_job(sig, command) 356 end 357 358 # Convenience method for Shell::CommandProcessor.def_system_command 359 def Shell.def_system_command(command, path = command) 360 CommandProcessor.def_system_command(command, path) 361 end 362 363 # Convenience method for Shell::CommandProcessor.undef_system_command 364 def Shell.undef_system_command(command) 365 CommandProcessor.undef_system_command(command) 366 end 367 368 # Convenience method for Shell::CommandProcessor.alias_command 369 def Shell.alias_command(ali, command, *opts, &block) 370 CommandProcessor.alias_command(ali, command, *opts, &block) 371 end 372 373 # Convenience method for Shell::CommandProcessor.unalias_command 374 def Shell.unalias_command(ali) 375 CommandProcessor.unalias_command(ali) 376 end 377 378 # Convenience method for Shell::CommandProcessor.install_system_commands 379 def Shell.install_system_commands(pre = "sys_") 380 CommandProcessor.install_system_commands(pre) 381 end 382 383 # 384 def inspect 385 if debug.kind_of?(Integer) && debug > 2 386 super 387 else 388 to_s 389 end 390 end 391 392 def self.notify(*opts) 393 Shell::debug_output_synchronize do 394 if opts[-1].kind_of?(String) 395 yorn = verbose? 396 else 397 yorn = opts.pop 398 end 399 return unless yorn 400 401 if @debug_display_thread_id 402 if @debug_display_process_id 403 prefix = "shell(##{Process.pid}:#{Thread.current.to_s.sub("Thread", "Th")}): " 404 else 405 prefix = "shell(#{Thread.current.to_s.sub("Thread", "Th")}): " 406 end 407 else 408 prefix = "shell: " 409 end 410 _head = true 411 STDERR.print opts.collect{|mes| 412 mes = mes.dup 413 yield mes if iterator? 414 if _head 415 _head = false 416# "shell" " + mes 417 prefix + mes 418 else 419 " "* prefix.size + mes 420 end 421 }.join("\n")+"\n" 422 end 423 end 424 425 CommandProcessor.initialize 426 CommandProcessor.run_config 427end 428