Skip to content
This repository


Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Easier shell tasks for ruby script.

branch: master

Fetching latest commit…

Cannot retrieve the latest commit at this time

Make shell task easier for ruby script.

Inspired by sh (for Python).


Basic usage

require 'easysh'
sh = EasySH.instant;            # ls
sh['/bin/ls']    # /bin/ls

Command-line parameters

EasySH automatically convert method names, symbols, hashes to meaningful parameters:

  • _method will be converted to -method
  • __method will be converted to --method
  • :symbol will be converted to --symbol
  • :s will be converted to -s
  • {:a => '1', :long => 2} will be converted to -a 1, --long=2
  • strings will be left untouched.'/bin')._l                 # ls /bin -l '/bin'                  # ls -l /bin '/bin', color: 'always' # ls /bin -l --color=always

EasySH supports method chaining and [params], method(params), just write in any form as you like:

sh['ls', '-l', :color => :always] '/bin', :l, :color => :always'/bin')['-l', :color => :always]'/bin')._l(:color => :always) => :always)['/bin']'/bin', :color => :always)._l

You can save command with parameters to variables for later use:

myls = :color => :always;
myls['/bin']  # note: myls '/bin' will not work since myls is an object, not a method

Commands can also be chained freely:

sudo = sh.sudo;

lab = sh.ssh.lab;      # or: sh.ssh 'lab', sh.ssh['lab'], sh.ssh('lab') '/bin'       # ssh lab ls -l /bin

You can pass arrays or EasySH objects(without pipes) as arguments to another EasySH object:

cmd    = sh.ifconfig.eth0;
opt    = ['mtu', 1440]
sudo[cmd].up           # sudo ifconfig eth0 up
sudo[cmd, opt].up      # sudo ifconfig eth0 up mtu 1440
# sudo[cmd |]   # Error: EasySH objects with pipes are not allowed here.

Ruby Enumerable

EasySH makes full use of Ruby's Enumerable. each_line (lines), each_char (chars), each_byte (bytes) are available like string. For convenience, each is an alias of each_line.

Use Enumerable for simple or complex tasks:                               # pick 5 chars randomly from `ls` output'euser,comm').map(&:split).group_by(&:first) # group process names by user name

EasySH handles endless stream correctly:'/dev/urandom').bytes.first(10)
sudo[sh.tail._f '/var/log/everything.log'].lines { |l| puts l.upcase }

You can even omit lines or each sometimes: { |l| puts l.upcase }
sudo.tail._f '/var/log/everything.log' do |l| puts l.upcase end

By not passing a block, you can use external iterator: (Note: in this case, make sure that the iteration does reach the end, otherwise background processes do not exit)

iter ='/sys/fs').lines             # 'btrfs'             # 'cgroup'             # 'ext4'             # 'fuse'             # StopIteration


Use < or > (Note: only one input redirect and one output redirect is supported currently):

sh.echo('hello') > '/tmp/test' < '/tmp/test' < '/tmp/abc' > '/tmp/def'

You can also associate file descriptor to file directly by using fd numbers => filename Hash (Note: for more information, see Process.spawn. EasySH will distinct Hash parameters from Hash redirects by checking if the Hash has any numeric key):

sh.echo 'hello', 1 => '/tmp/stdout', 2 => '/tmp/stderr' 0 => '/tmp/test'


Use | (Note: redirects except the rightmost output and leftmost input will be ignored) :

( | sh.head(n: 5)).each { |l| puts l.upcase }'ls') | sh.tail(n: 30) | sh.head(:n, 4)       # man ls | tail -n 30 | head -n 4
( < '/tmp/abc') | | > '/tmp/def' # cat < /tmp/abc | cat | cat > /tmp/def

EasySH objects connected with pipes can be saved for later use:

grep   = sh['grep'];   # sh.grep does not work because grep is provided by Enumerable
filter = grep['problem'] | grep._v['bugs']; | filter

Since EasySH does some lazy evaluation. You can add parentheses in anywhere in any order:

kat =;
kat['/tmp/foo'] | (kat | kat | kat.|(kat) | (kat | kat) | (kat | kat))

Exit status

Use exitcode or to_i to get exitcode directly:

sh.true.exitcode   # => 0
sh.false.to_i      # => 1

successful? is exitcode == 0 and failed? is exitcode != 0

grep = sh['grep', :q];
(sh.echo.hello | grep['world']).failed?     # => true
( | grep['world']).successful? # => true

Use status method to get a Process::Status object about last run status:

p = sh.which('bash')
p.status        # => #<Process::Status: pid 5931 exit 0>
p = sh.which.nonexists
p.status        # => #<Process::Status: pid 6156 exit 1>

More sugars

An EasySH object behaves like an Array or a String sometimes.

If you pass arguments like: [int], [int, int], [range]; [regex], [regex, int], then to_a or to_s will be automatically called.

# like Array
sh.echo("Line 1\nLine 2\nLine 3")[1]    # => "Line 2"
sh.echo("Line 1\nLine 2\nLine 3")[-1]   # => "Line 3"
sh.echo("Line 1\nLine 2\nLine 3")[0, 2] # => ["Line 1", "Line 2"]
sh.echo("Line 1\nLine 2\nLine 3")[1..2] # => ["Line 2", "Line 3"]

# like String
sh.echo("Hello world\nThis is a test")[/T.*$/]            # => "This is a test"
sh.echo("Hello world\nThis is a test")[/T.* ([^ ]*)$/, 1] # => "test"

Instant mode

EasySH object with instant = true will execute command when inspect is called, which is useful in REPL environment like pry or irb.

If you like traditional inspect behavior, you can create the sh object using:

sh =

or set instant to false:

sh.instant = false

With instant = false, you need additional to_s or to_a or to_i etc. to get command executed.

[2] pry(main)> sh =; sh.uname
=> #<EasySH: uname>
[2] pry(main)> sh.uname.to_s
=> "Linux"


gem install easysh

You may want to put these lines in .pryrc or .irbrc:

require 'easysh'
sh = EasySH.instant
Something went wrong with that request. Please try again.