Páginas

Showing posts with label Bash. Show all posts
Showing posts with label Bash. Show all posts

Saturday, August 27, 2011

Integrating git and rvm with prompt

Here's a quick hack to your prompt, that I find helpful when using git and rvm.

Add this to you .bash_profile, .bashrc, or whatever file you use to configure your shell:

export PROMPT_COMMAND='PS1="\[\033[0;33m\][\!] \h\`
if [[ \$? = "0" ]]; then echo "\\[\\033[32m\\]";
else echo "\\[\\033[31m\\]"; fi
\` \`
if [[ `pwd|wc -c|tr -d " "` > 60 ]]; then echo ":..$(expr "$PWD" : ".*\(/.*.\{40\}\)")";
else echo ${PWD/\/Users\/$(whoami)/\~}; fi
\`\[\] $(parse_git_branch)\[\]\[\033[0m\]$(rvm_version.sh)\[\033[0m\]: ";
echo -ne "\033]0;`hostname -s`:`pwd`\007"'


Now you need the parse_git_branch and rvm_version.sh.

Parse Git Branch

parse_git_branch() {
git branch 2> /dev/null | sed -e '/^[^*]/d' -e 's/* \(.*\)/[\1]/'
}


RVM Version

ruby_version=$(/usr/local/rvm/bin/rvm current)
if [ -f "Gemfile" ] # If it is a rails project
then
  if [ -e "app/assets" ] # If rails version >= 3.1
  then
    if [[ $ruby_version != *1.9* ]] # If not using correct rvm version
    then
      echo -n "\\[\\033[31m\\]"
    else
      echo -n "\\[\\033[32m\\]"
    fi
  else # If rails version >= 3.1
    if [[ $ruby_version != *1.8* ]]
    then
      echo -n "\\[\\033[31m\\]"
    else
      echo -n "\\[\\033[32m\\]"
    fi
  fi
  echo -n [$(/usr/local/rvm/bin/rvm current)]
else
  echo -n "\\[\\033[32m\\]"
fi


Put them somewhere in some directory that's in your PATH, and it should work. What it does, as far as git and rvm go, is tell what branch your on, and if your using the correct rvm for your rails project (turns red if your not).

I'll probably work a little more on the rvm_version, that is the only thing that I can actually get credit for, but for now it works.

Friday, February 18, 2011

Opening a new tab in the same directory and then some in Mac OS

For all of you that use the Mac OS Terminal, you've probably felt the frustation of opening a new tab and it opening on the $HOME path, unlike the Linux one's, that open in the path you were in.

Well, I've written a script that kind of solves this problem, and adds some extra functionality that I find really helpfull.


#!/bin/bash

COMMAND=$1

if [ -z "$1" ]
then
  COMMAND="cd $(pwd)"
fi

/usr/bin/osascript 2>/dev/null <<EOF
activate application "Terminal"
tell application "System Events"
  keystroke "t" using {command down}
end tell
tell application "Terminal"
  activate
  do script "$COMMAND" in window 1
end tell
return
EOF


First let's take a look at the applescript part (that's the part between EOF). Applescript code is very readable, but what it does is to open a new tab in the terminal, and then run the code in the COMMAND variable in that newly open window.

Then, there is that little bit of bash code, that assigns the string passed as an argument to be run or, if none is provided, it changes the directory to the one you were in.

So, you can open a new tab, by calling the script or, and this is the very handy thing for writing other scripts, open a new tab and run code in that tab, by calling the script with the string as an argument.

Sunday, October 3, 2010

Bash 101: Variables and Conditions

First off I would like to make a little note on the use of quotes. In the shell, variables are separated by whitespaces, if you want those characters to belong to the variable you'll have to quote them.

There are 3 types of quotes, double, single and the backslash, that have the following results:
  • Double - Accepts whitespaces and expands other variables
  • Single -   Accepts whitespaces and doesn't expand other variables
  • Backslash - Escapes the value of $
In my previous post I've talked about normal variables, here I'll talk about the other two types of variables, environment and parameter.

Environment Variables


At the start of any shell script some variables are initialized with values defined in the environment (you can change them with export or in the .bash_profile file). For convenience these variables are all uppercase, as opposed to the user defined that should be lowercase. Here's a list of the main ones and a brief description.
  • $HOME - Home directory of the current user
  • $PATH - The list of directories to search commands
  • $PS1 - The definition of the command prompt (eg: \h:\W \u\$)
  • $PS2 - The secondary prompt, usually >
  • $IFS - Input Field Separator. List of characters used to separate words when reading input
  • $0 - The name of the shell script
  • $# - The number of parameters passed
  • $$ - The PID of the shell script (normally used to create temporary files)
If you want to check all your environment variables just type printenv in the shell.

IBM has a pretty good hands on post to understand the setting and unsetting of environment variables, here, and refer to this other post for a more extensive overview of variables.

Parameter Variables


If your scripts is invoked with parameters it has some more variables which are defined (you can check if there are any parameters if the $# variable has a value greater than 0). These are the parameter variables:
  • $1, $2, ... - The parameters given to the script in order
  • $* - A list of all parameters in a single variables, separated by the first character in IFS
  • $@ - A variation of $*, that always uses a space to separate the parameters
I've written a small script that should make the difference between $* and $@ clear:

#!/bin/bash

export IFS=*
echo "IFS = $IFS"
echo "With IFS - $*"
echo "Without IFS - $@"

exit 0

Run it as ./script param1 param2 param3 ...

Note: The export command sets the variable for the script and all its subordinates.

Conditions


One the fundamental things of any programming language is the ability to test conditions. A shell script can test the exit code of any command it invokes, even of scripts written by you. That is why it is very important to include an exit command with a value (0 if it is ok), at the end of all your scripts.

The commands used to test conditions are two synonyms, test and [. Obviously, if you use [ it must have a matching ], and because it makes your code much easier to read, this is the most used construct.

In a shell script, a test should look something like

if [ -f file ]
then
    echo "File exists"
else
    echo "File does not exist"
fi

The exit code of either these commands is what determines the veracity or not of the statement (again, 0 for true and 1 for false). A little thing to remember is that [ is a command, therefore you must put spaces between it and the condition, or else it won't work.

There are 3 types of conditions that can be used with these commands:

String Comparison

  • string1 = string2 - True if strings are equal
  • string1 != string2 - True if strings are not equal
  • -n string - True if string is not null
  • -z string - True if string is null (empty)

Arithmetic Comparison

  • exp1 -eq exp2 - True if the expressions are equal
  • exp1 -ne exp2 - True if the expressions are not equal
  • exp1 -gt exp2 - True if exp1 is greater than exp2
  • exp1 -ge exp2 - True if exp1 is greater than or equal to exp2
  • exp1 -lt exp2 - True if exp1 is less than exp2
  • exp1 -le exp2 - True if exp1 is less than or equal to exp2
  • ! exp - True if the expression is false and vice versa

File Conditional

  • -d file - True if the file is a directory
  • -e file - True if the file exists (-f is usually used instead)
  • -f file - True if the file is a regular file
  • -g file - True if set-group-id is set on file
  • -u file - True if the set-user-id is set on file
  • -s file - True if the file has nonzero size
  • -r file - True if the file is readable
  • -w file - True if the file is writable
  • -x file - True is the file is executable
These are the more commonly used options, for a complete list type help test in your bash.

Tuesday, September 14, 2010

Bash 101: The Shell as a Programming Language

There are two different ways of writing shell programs, interactively (type a sequence of commands on the shell and let it execute them), or store the commands in a file that can be invoked as a program. We'll focus on the later, but having in mind that they are pretty much alike.

The first little "trick" you should be aware of are the wildcard expansions (or globbing), here are some of the most used:
  • * - Matches any string of characters
  • ? - Matches any character
  • [...] - Matches the defined characters
  • [^...] - Negates the previous one (everything but what matches)
  • {...} -  Matches the specified strings
Here's an example of the last one:
ls my_{file,doc}s

Which will list the files my_files and my_docs.

There is another thing you should know before starting to write bash scripts and that's the $(...) operation. What it does is represent the output of the program you invoke inside the brackets. Let's see it with an example:

Note:
  1. If the string contains spaces it must be delimited by quote marks;
  2. There can't be any spaces before or after the equal sign.
You can also assign user input to a variable, using read. It waits for the user to write something and press Enter. At that moment the variable has been assigned what the user wrote:

$read variable
abcde (Enter)
$echo $variable
abcde


At this time you're ready for your first program. In order to do that just open your favorite text editor and write the commands.

Comments in bash are represented by the character #, and continue to the end of the line. The only exception is the first line that should start with #! and the path to the program used to run the file (usually /bin/bash). Also by convention the last line of the file should be exit 0 (for now just know that 0 represents successful in shell programming).

The actual script could be something like this:

#!/bin/bash

echo "Name of file to cat:"
read file
cat $file

greeting="\nHello World"
echo -e $greeting

exit 0


This script waits for the name of a file, "cats" it and then writes Hello World to the output (not worrying with errors such as the file not existing).

The final step is to make the file executable, with chmod:
$chmod +x file
and run it!
$./filename

Saturday, August 28, 2010

How to create a man page

OA very useful thing, as everyone that has ever used a shell knows, are the man pages. In this post I'll explain how to create a man page for your programs, from scratch.

First you have to create a file and write the actual text for the man page, using some special tags, the most important can be found here. This is the main step of the creation of the man page, and where you should spend most of the time.

So that you can check how your man page will look while you're writing it and/or after you've written it, there is a very useful program, nroff, that is used the following way:

nroff -e -mandoc yourFile | less -s

Once you feel your page is as you want it, you'll have to rename your file to XXX.1 (or any other number from 1 to 8, according to this standard) and then compress it using gzip or bzip2. Your man page is now ready!

If you try the command man yourFile, you'll notice it doesn't work. That's because you're man page isn't in the MANPATH yet. To do this you'll first have to move your file to a directory with the name manX, being X the number you chose from the 8 possible (it is normal to have a directory called man and all the manX directories to be it's children). So, let's do that:

mkdir path/to/program/man
mkdir path/to/program/man/man1
mv yourFile.1.gz path/to/program/man/man1


The last thing to be done is actually adding your man folder to the MANPATH, if it isn't already added. The first thing to do is check if you have to add it or not:

echo $MANPATH (Just to check if the folder is in the current MANPATH)
export MANPATH=path/to/program/man:$MANPATH


Instead of doing this, if you have root privileges, you can create a directory in one of the directories already pointed by the MANPATH, like /usr/share/man.

And that's it! Go try it out!

Sunday, August 15, 2010

Bash 101 - I/O Redirection and Pipes

Bash scripting is a subject I'm really fond of, but before starting to write scripts one needs to understand how the bash commands work and how they can be connected. That's the purpose of this post, to provide some background before getting into the actual writing of scripts.

I/O Redirection


Many commands such as ls print their output on the display. We can change this using operators under the I/O redirection category, to redirect the output of those commands to files, devices and also to the input of other commands.

There are 3 file descriptors, stdin (standard input), stdout (standard output) and stderr (standard error). Basically you can use redirection in 7 different ways:
  1. stdout 2 file
  2. stderr 2 file
  3. stdout 2 stderr
  4. stderr 2 stdout
  5. stderr and stdout 2 file
  6. stderr and stdout 2 stdout
  7. stderr and stdout 2 stderr
This file descriptors are represented by the numbers 0 (stdin), 1 (stdout) and 2 (stderr).
To make this clearer, let's give examples for each of the redirections.
  1. ls -l > output.txt
  2. grep something * 2> errors.txt
  3. ls -l 1>&2
  4. grep something * 2>&1
  5. rm -f $(find / -name core) &> /dev/null (redirects both stdout and stderr to /dev/null that automatically deletes that information)

Other cases:

> filename - this truncates the file to zero length and if the file is not present has the same effect as touch

>> filename - this operator can be used as normal redirection to a file, but instead of writing from the start it appends to the end of that file

command < filename - accepts input from a file

3<> filename - open file for reading and writing and assigns file descriptor 3 to it

n <&- - closes file descriptor n (this is useful with pipes, because child processes inherit open file descriptors)


Another kind of redirection are the HERE documents that I've mentioned before. With those you can redirect a dynamic file to the input of a program that receives a normal file. An example of this can be seen in my earlier post.

Pipes


Very simply put, pipes let you use the output of a program as the input of another. To do this, a process is created for each command. That's why being careful with which file descriptors are open at the moment of creation (before sending the output as input) of each of those processes is important.

The basic functionality of pipes can be understood with a very simple example that has the same effect as ls -l *.sh:

ls -l  |  grep "\.sh$"

Here, the output of the program ls -l is sent to the grep program, which, in turn, will print lines which match the regex "\.txt$".

Friday, July 23, 2010

Using HERE documents to do repetitive programming

In recent work I had to write Factories for a lot of classes in diferent packages, these factories all had the same general format and so, because it would be really boring to code this by hand, I wrote a little script that takes advantage of HERE documents. There were two types of files, the interfaces and the actual implementations, recognized by having the Impl sufix.

The first thing is to get the paths of the interfaces, we do this with an inverted grep:

for path in $(find . -type f | grep -v Impl)

Then we get the path to directory, excluding the name of the actual file:

factoryPath=${path%/*.java}

After that we get the file name:

javaFile=${path##*/}

and the class name (the name of the file without the .java)

className=${javaFile%.java}

We now have all the basic blocks needed to build our script. From now on it's just a matter of how to combine them. From the class name we get the factory file name:

factoryFile=${className}Factory.java

The package name for the interfaces is the factoryPath variable, but changing the / for .

packageName=$(echo ${factoryPath##./} | tr '/' '.')

and the one for the actual implementation is:

packageImpl=${packageName}.impl

The next step is to create the file we are going to write into:

touch ${factoryPath}/impl/${factoryFile}

And finally let's write the file using the HERE document:

cat >${factoryPath}/impl/${factoryFile} <<HERE
package ${packageImpl};  
  

import ${packageName}.${className};
import tsl.jaxb.${className}XMLImpl;    
  
public class ${className}Factory {
    public static ${className} getInstance(String instance)

    {
        if(instance.equals("XML"))
            return new ${className}XMLImpl();
        if(instance.equals("DER"))

            return new ${className}DERImpl();
  
        return null;
    }
}  

HERE