Ruby thor command line tool commands - ruby

I am planning on making my first command line tool and was wondering about how the naming and commands work.
I would like my tool to function similarly to Git, in the sense that you just install it, then to run commands you just enter git clone or git commit. In many of the examples I have seen the tools are something like thor foo:bar or ./foo.rb bar.
My main question is how can I make it so if my tools name is Foo, and the command in my tool is bar, all the user has to do is run Foo bar in the command line.

In the case of git clone the way it works is that git program is executed and clone is passed to it as a parameter. So, if you have several scripts that should be started in a similar manner, you can create a small launcher. This code is in bash(but the script is sh compatible, so you can safely change the shebang to /bin/sh). It is very easy to write the same thing in any other language.
#!/bin/bash
command=$1
shift
case $1 in
cmd1)
echo "Executing command #1"
./smallcmd1 "$#"
;;
cmd2)
echo "Executing command $command"
./smallcmd2 "$#"
;;
'')
echo 'No option specified'
exit 1
;;
*) echo "Invalid option"
exit 1
;;
esac
exit $?
Where smallcmds are your secondary command scripts or programs.
Now you can execute it:
./mycommand smallcmd1
Additional parameters can be passed as well.
If you place this script into any $PATH directory then you can omit ./ like so:
mycommand smallcmd1

Making an executable with Thor is really simple. All you have to do is:
include the ruby shebang line.
require "thor" in your script.
define your Thor class.
add #{YourThorClassname}.start to the bottom of your script.
Example: my-cli.rb
#!/usr/bin/env ruby
require "thor"
class MyCLI < Thor
desc "foo", "Prints foo"
def foo
puts "foo"
end
end
MyCLI.start
Make the script executable:
chmod a+x my-cli.rb
Now you can type:
./my-cli.rb foo
You can find a similar example and more help in the Thor Wiki.
You can even rename the file to my-cli (without the .rb extension) and it still works because of the ruby shebang inside the file.

Related

Mix input/output with Ruby IO?

I am hoping to write a small method that can interact with a subprocess (bash in this case) and should be able to both write commands and have those commands print their outback back to my shell when running the Ruby file.
So far, I can do something similar with this code:
require 'io/console'
#shell = IO.popen('/bin/bash', 'w')
def run(command)
puts command
#shell.puts command
puts 'Done'
end
run 'var=3'
run 'echo $var'
run 'sleep 2'
run 'ls docs'
#shell.close
And then when I run this code all of the Ruby code is printed first, and only later does any of the shell code get printed:
var=3
Done
echo $var
Done
sleep 2
Done
ls docs
Done
3
<ls output>
I was trying to read some of the tests for io/console as I'm almost certain there exists a really straightforward way to interact with a subprocess like this and get the output inline with the commands being run:
https://github.com/ruby/io-console/blob/master/test/io/console/test_io_console.rb

Run sh scripts successively

I'd like to write .sh script that runs several scripts in the same directory one-by-one without running them concurrently (e.x. while the first one is still executing, the second one doesn't start executing).
Could you tell me the command, that could be written in front of script's name that does the actual thing?
I've tried source but it gives the following message for every listed script
./outer_script.sh: source: not found
source is a non-standard extension introduced by bash. POSIX specifies that you must use the . command. Other than the name, they are identical.
However, you probably don't want to source, because that is only supposed to be used when you need the script to be able to change the state of the script calling it. It is like a #include or import statement in other languages.
You would usually want to just run the script directly as a command, i.e. do not prefix it with source nor with any other command.
As a quick example of not using source:
for script in scripts/*; do
"$script"
done
If the above does not work, ensure that you've set the executable bit (chmod a+x) on the necessary scripts.
That is normal behavior of the bash script. i.e. if you have three scripts:
script1.sh:
echo "starting"
./script2.sh
./script3.sh
echo "done"
script2.sh:
while [ 1 ]; do
echo "script2"
sleep 2
done
and script3.sh:
echo "script3"
The output is:
starting
script2
script2
script2
...
and script3.sh will never be executed, unless you modify script1.sh to be:
echo "starting"
./script2.sh &
./script3.sh &
echo "done"
in which case the output will be something like:
starting
done
script2
script3
script2
script2
...
So in this case I assume your second level scripts contain something that starts new processes.
Have you included the line #!bin/bash in your outer_script? Some OS's don't consider it to be bash by default and source is bash command. Else just call the scripts using ./path/to/script.sh inside the outer_script

Change directory in fish function and return to original directory after abort

I am switching from bash to fish, but am having trouble porting over a convenience function I use often. The point of this function is to run make from the root directory of my source tree regardless of which directory my shell is currently in.
In bash, this was simply:
function omake {(
cd $SOURCE_ROOT;
make $#;
)}
Since fish doesn't have subshells, the best I've been able to do is:
function omake
pushd
cd $SOURCE_ROOT
make $argv
popd
end
This works, but with the caveat that after interrupting the fish version with ^C, the shell is still in $SOURCE_ROOT, but interrupting the bash version puts me back in the original directory.
Is there a way to write a script that works identically to the bash one in fish?
This is as close as I can get to a subshell:
function omake
echo "cd $SOURCE_ROOT; and make \$argv" | fish /dev/stdin $argv
end
Process substitution does not seem to be interruptable: Ctrl-C does not stop this sleep cmd
echo (cd /tmp; and sleep 15)
However, fish has a very nice way to find the pid of a backgrounded process:
function omake
pushd dir1
make $argv &
popd
end
Then, to stop the make, instead of Ctrl-C, do kill %make
if you're using GNU coreutils, you can use env to do this (as user2394284 suggested would be nice).
env -C foo pwd
this will run pwd in a subdirectory called foo nicely. this generally interacts nicely with fish, for example it can be backgrounded nicely
the docs say:
Change the working directory to dir before invoking command. This differs from the shell built-in cd in that it starts command as a subprocess rather than altering the shell’s own working directory; this allows it to be chained with other commands that run commands in a different context.
For make specifically, you can use its -C option:
function omake
make -C $SOURCE_ROOT $argv
end
Many other programs take the -C option. On top of my head: ninja, git
Otherwise, if you're content with a subshell anyway, a standalone script gives you just that, with the benefit that you can write it in any language, from Python to C, and won't have to rewrite it when you want to change your shell.
This could be written in any language:
#!/usr/bin/env fish
cd $SOURCE_ROOT
and exec make $argv
However, what really bugs me is the blatant omission of the -C option in env! Env can set environment variables and run programs; clearly, it would be nice to be able to set the working directory too! Here is a rudimentary cdrun script to make up for that:
#!/usr/bin/env fish
cd $argv[1]
and exec env $argv[2..-1]
or
#!/bin/sh
set cd="$1"
shift
cd "$cd" &&
exec env "$#"
Armed with the cdrun command, your omake function would be as simple as:
function omake
cdrun $SOURCE_ROOT make $argv
end
or
omake() {
cdrun "$SOURCE_ROOT" make "$#"
}

Shell: How to call one shell script from another shell script?

I have two shell scripts, a.sh and b.sh.
How can I call b.sh from within the shell script a.sh?
There are a couple of different ways you can do this:
Make the other script executable with chmod a+x /path/to/file(Nathan Lilienthal's comment), add the #!/bin/bash line (called shebang) at the top, and the path where the file is to the $PATH environment variable. Then you can call it as a normal command;
Or call it with the source command (which is an alias for .), like this:
source /path/to/script
Or use the bash command to execute it, like:
/bin/bash /path/to/script
The first and third approaches execute the script as another process, so variables and functions in the other script will not be accessible.
The second approach executes the script in the first script's process, and pulls in variables and functions from the other script (so they are usable from the calling script).
In the second method, if you are using exit in second script, it will exit the first script as well. Which will not happen in first and third methods.
Check this out.
#!/bin/bash
echo "This script is about to run another script."
sh ./script.sh
echo "This script has just run another script."
There are a couple of ways you can do this. Terminal to execute the script:
#!/bin/bash
SCRIPT_PATH="/path/to/script.sh"
# Here you execute your script
"$SCRIPT_PATH"
# or
. "$SCRIPT_PATH"
# or
source "$SCRIPT_PATH"
# or
bash "$SCRIPT_PATH"
# or
eval '"$SCRIPT_PATH"'
# or
OUTPUT=$("$SCRIPT_PATH")
echo $OUTPUT
# or
OUTPUT=`"$SCRIPT_PATH"`
echo $OUTPUT
# or
("$SCRIPT_PATH")
# or
(exec "$SCRIPT_PATH")
All this is correct for the path with spaces!!!
The answer which I was looking for:
( exec "path/to/script" )
As mentioned, exec replaces the shell without creating a new process. However, we can put it in a subshell, which is done using the parantheses.
EDIT:
Actually ( "path/to/script" ) is enough.
If you have another file in same directory, you can either do:
bash another_script.sh
or
source another_script.sh
or
. another_script.sh
When you use bash instead of source, the script cannot alter environment of the parent script. The . command is POSIX standard while source command is a more readable bash synonym for . (I prefer source over .). If your script resides elsewhere just provide path to that script. Both relative as well as full path should work.
Depends on.
Briefly...
If you want load variables on current console and execute you may use source myshellfile.sh on your code. Example:
#!/bin/bash
set -x
echo "This is an example of run another INTO this session."
source my_lib_of_variables_and_functions.sh
echo "The function internal_function() is defined into my lib."
returned_value=internal_function()
echo $this_is_an_internal_variable
set +x
If you just want to execute a file and the only thing intersting for you is the result, you can do:
#!/bin/bash
set -x
./executing_only.sh
bash i_can_execute_this_way_too.sh
bash or_this_way.sh
set +x
You can use /bin/sh to call or execute another script (via your actual script):
# cat showdate.sh
#!/bin/bash
echo "Date is: `date`"
# cat mainscript.sh
#!/bin/bash
echo "You are login as: `whoami`"
echo "`/bin/sh ./showdate.sh`" # exact path for the script file
The output would be:
# ./mainscript.sh
You are login as: root
Date is: Thu Oct 17 02:56:36 EDT 2013
First you have to include the file you call:
#!/bin/bash
. includes/included_file.sh
then you call your function like this:
#!/bin/bash
my_called_function
Simple source will help you.
For Ex.
#!/bin/bash
echo "My shell_1"
source my_script1.sh
echo "Back in shell_1"
Just add in a line whatever you would have typed in a terminal to execute the script!
e.g.:
#!bin/bash
./myscript.sh &
if the script to be executed is not in same directory, just use the complete path of the script.
e.g.:`/home/user/script-directory/./myscript.sh &
This was what worked for me, this is the content of the main sh script that executes the other one.
#!/bin/bash
source /path/to/other.sh
The top answer suggests adding #!/bin/bash line to the first line of the sub-script being called. But even if you add the shebang, it is much faster* to run a script in a sub-shell and capture the output:
$(source SCRIPT_NAME)
This works when you want to keep running the same interpreter (e.g. from bash to another bash script) and ensures that the shebang line of the sub-script is not executed.
For example:
#!/bin/bash
SUB_SCRIPT=$(mktemp)
echo "#!/bin/bash" > $SUB_SCRIPT
echo 'echo $1' >> $SUB_SCRIPT
chmod +x $SUB_SCRIPT
if [[ $1 == "--source" ]]; then
for X in $(seq 100); do
MODE=$(source $SUB_SCRIPT "source on")
done
else
for X in $(seq 100); do
MODE=$($SUB_SCRIPT "source off")
done
fi
echo $MODE
rm $SUB_SCRIPT
Output:
~ ❯❯❯ time ./test.sh
source off
./test.sh 0.15s user 0.16s system 87% cpu 0.360 total
~ ❯❯❯ time ./test.sh --source
source on
./test.sh --source 0.05s user 0.06s system 95% cpu 0.114 total
* For example when virus or security tools are running on a device it might take an extra 100ms to exec a new process.
pathToShell="/home/praveen/"
chmod a+x $pathToShell"myShell.sh"
sh $pathToShell"myShell.sh"
#!/bin/bash
# Here you define the absolute path of your script
scriptPath="/home/user/pathScript/"
# Name of your script
scriptName="myscript.sh"
# Here you execute your script
$scriptPath/$scriptName
# Result of script execution
result=$?
chmod a+x /path/to/file-to-be-executed
That was the only thing I needed. Once the script to be executed is made executable like this, you (at least in my case) don't need any other extra operation like sh or ./ while you are calling the script.
Thanks to the comment of #Nathan Lilienthal
Assume the new file is "/home/satya/app/app_specific_env" and the file contents are as follows
#!bin/bash
export FAV_NUMBER="2211"
Append this file reference to ~/.bashrc file
source /home/satya/app/app_specific_env
When ever you restart the machine or relogin, try echo $FAV_NUMBER in the terminal. It will output the value.
Just in case if you want to see the effect right away, source ~/.bashrc in the command line.
There are some problems to import functions from other file.
First: You needn't to do this file executable. Better not to do so!
just add
. file
to import all functions. And all of them will be as if they are defined in your file.
Second: You may be define the function with the same name. It will be overwritten. It's bad. You may declare like that
declare -f new_function_name=old_function_name
and only after that do import.
So you may call old function by new name.
Third: You may import only full list of functions defined in file.
If some not needed you may unset them. But if you rewrite your functions after unset they will be lost. But if you set reference to it as described above you may restore after unset with the same name.
Finally In common procedure of import is dangerous and not so simple. Be careful! You may write script to do this more easier and safe.
If you use only part of functions(not all) better split them in different files. Unfortunately this technique not made well in bash. In python for example and some other script languages it's easy and safe. Possible to make partial import only needed functions with its own names. We all want that in next bush versions will be done the same functionality. But now We must write many additional cod so as to do what you want.
Use backticks.
$ ./script-that-consumes-argument.sh `sh script-that-produces-argument.sh`
Then fetch the output of the producer script as an argument on the consumer script.

Bash script exiting prematurely when calling another script inside it

I have a bash script which calls another bash script, like so:
#!/bin/bash
echo "Hi"
./script-two.sh
echo "Hello!"
The problem that I have is that it never makes it to printing "Hello!"
I think this is because ./script-two.sh (Which I did not write) is somehow exiting or changing the shell. I have included this script at the end of this post.
Is there a way I can gurentee that my execution will continue after script-two.sh executes?
I have looked into using the trap command, but I don't fully understand its use properly.
Thanks,
Casey
Here is the contents of what would be script-two.sh
#!/bin/sh
# This file is part of the DITA Open Toolkit project hosted on
# Sourceforge.net. See the accompanying license.txt file for
# applicable licenses.
# (c) Copyright IBM Corp. 2006 All Rights Reserved.
export DITA_HOME=cwd
if [ "${DITA_HOME:+1}" != "1" ]; then
echo "DITA_HOME environment variable is empty or not set";
exit 127;
fi
echo $DITA_HOME
cd "$DITA_HOME"
# Get the absolute path of DITAOT's home directory
DITA_DIR="`pwd`"
echo $DITA_DIR
if [ -f "$DITA_DIR"/tools/ant/bin/ant ] && [ ! -x "$DITA_DIR"/tools/ant/bin/ant ]; then
chmod +x "$DITA_DIR"/tools/ant/bin/ant
fi
export ANT_OPTS="-Xmx512m $ANT_OPTS"
export ANT_OPTS="$ANT_OPTS -Djavax.xml.transform.TransformerFactory=net.sf.saxon.TransformerFactoryImpl"
export ANT_HOME="$DITA_DIR"/tools/ant
export PATH="$DITA_DIR"/tools/ant/bin:"$PATH"
NEW_CLASSPATH="$DITA_DIR/lib:$DITA_DIR/lib/dost.jar:$DITA_DIR/lib/commons-codec-1.4.jar:$DITA_DIR/lib/resolver.jar:$DITA_DIR/lib/icu4j.jar"
NEW_CLASSPATH="$DITA_DIR/lib/saxon/saxon9.jar:$DITA_DIR/lib/saxon/saxon9-dom.jar:$NEW_CLASSPATH"
NEW_CLASSPATH="$DITA_DIR/lib/saxon/saxon9-dom4j.jar:$DITA_DIR/lib/saxon/saxon9-jdom.jar:$NEW_CLASSPATH"
NEW_CLASSPATH="$DITA_DIR/lib/saxon/saxon9-s9api.jar:$DITA_DIR/lib/saxon/saxon9-sql.jar:$NEW_CLASSPATH"
NEW_CLASSPATH="$DITA_DIR/lib/saxon/saxon9-xom.jar:$DITA_DIR/lib/saxon/saxon9-xpath.jar:$DITA_DIR/lib/saxon/saxon9-xqj.jar:$NEW_CLASSPATH"
if test -n "$CLASSPATH"
then
export CLASSPATH="$NEW_CLASSPATH":"$CLASSPATH"
else
export CLASSPATH="$NEW_CLASSPATH"
fi
"$SHELL"
It looks like script-two.sh is setting up an ant build environment.
I think the author intended that it sets up the build environment, then you type your build commands in manually, then type exit to leave the build environment.
I say this because the bottom line of script-two.sh is:
"$SHELL"
which starts a new shell.
Try running your script, then type exit. I think you will see it print Hello! after you type exit.
I'm guessing you're trying to do something like:
#!/bin/bash
echo "Hi"
./script-two.sh
ant <some args>
To do that, what you really want to do is source it, by changing:
./script-two.sh
to
. script-two.sh
e.g.
#!/bin/bash
echo "Hi"
. script-two.sh
ant <some args>
But, you will need to edit script-two.sh and change:
"$SHELL"
to:
case $0 in *script-two.sh)
# executed, start a new shell with the new environment
"$SHELL"
;;
*)
# sourced, don't start a new shell
;;
esac
so that it only starts a shell if the script is being run like ./script-two.sh, but not if it is being sourced like . script-two.sh.
Or if you absolutely can't change script-two.sh, then you could do:
#!/bin/bash
echo "Hi"
. script-two.sh </dev/null
ant <some args>
which will trick "$SHELL" into exiting because it has no input.
Also
export DITA_HOME=cwd
doesn't seem right to me.
It should probably be
export DITA_HOME=$(pwd)
or
export DITA_HOME=`pwd`
(both are equivalent)
I had a similar problem today, up on digging I finally found the answer.
The script I was calling (from within my script) actually had an exit 0 in the end. Removing that fixed my issues.
Just leaving this here as someone may find it useful.
Well for starters, you can execute your bash script with the -x switch to see where it is failing:
bash -x script-one.sh
Secondly, if you call the second script like this:
#!/bin/bash
echo "Hi"
var=$(bash script-two.sh)
echo "Hello!"
It will continue, as long as script-two.sh exits cleanly. Again, you can run the -x script against that script find any problems.
And as Mikel mentioned, always make sure to have exit at the bottom of your scripts.

Resources