Category Archives: bash

Synchronize Directories in Bash Shell

The Problem

I often work in a Linux environment with multiple windows open in a tmux session. One of the pattern I see often is the need to navigate to the same directory for multiple windows. For example, in window 1, I navigated to a directory deep within my project:

$ cd ~/long/path/to/my/directory

Then, on window 2, I want to navigate to the same directory. This often involves either copy the command from window 1 and paste into window 2, or retype the same command again. There must be a better way.

Continue reading

Advertisements

Let the Mac Speak for Me

The Problem

I often lose my voice temporarily due to allergy. During that time, my main mode of communication is a notepad, or the electronic equivalent: Zen Brush. I was looking for a solution that can convert what I type into spoken words.

The Solution

Since I am using Mac both at home and at work, and Mac has the say command which is useful for this purpose; I decided to roll my own solution. It turned out that the solution is a very simple bash script, which I named speak4me.sh, which I saved in my ~/bin directory:

#!/bin/bash
while read line
do
    say -v Alex $line
done

To make the script executable, I issued the following command:

$ chmod +x ~/bin/speak4me.sh

Using speak4me.sh

To use speak4me.sh, from the terminal, issue the following command:

$ speak4me.sh

After that, start typing your message. As soon as you hit the return key, the script will “say” what you type. You can keep on typing and hitting return. To end the script, just type Ctrl+C.

Discussion

The script is very simple, pragmatic and free from bells and whistles, but it works the way I like it. The -v Alex part of the say command specifies a voice from Alex. OS X comes with a few voices which you can experiment with yourself. To list the voices your system has, issue the following command:

$ say -v ?
Agnes               en_US    # Isn't it nice to have a computer that will talk to you?
Albert              en_US    #  I have a frog in my throat. No, I mean a real frog!
Alex                en_US    # Most people recognize me by my voice.
Bad News            en_US    # The light you see at the end of the tunnel is the headlamp of a fast approaching train.
Bahh                en_US    # Do not pull the wool over my eyes.
Bells               en_US    # Time flies when you are having fun.
Boing               en_US    # Spring has sprung, fall has fell, winter's here and it's colder than usual.
Bruce               en_US    # I sure like being inside this fancy computer
Bubbles             en_US    # Pull the plug! I'm drowning!
Cellos              en_US    # Doo da doo da dum dee dee doodly doo dum dum dum doo da doo da doo da doo da doo da doo da doo
Deranged            en_US    # I need to go on a really long vacation.
Fred                en_US    # I sure like being inside this fancy computer
Good News           en_US    # Congratulations you just won the sweepstakes and you don't have to pay income tax again.
Hysterical          en_US    # Please stop tickling me!
Junior              en_US    # My favorite food is pizza.
Kathy               en_US    # Isn't it nice to have a computer that will talk to you?
Pipe Organ          en_US    # We must rejoice in this morbid voice.
Princess            en_US    # When I grow up I'm going to be a scientist.
Ralph               en_US    # The sum of the squares of the legs of a right triangle is equal to the square of the hypotenuse.
Trinoids            en_US    # We cannot communicate with these carbon units.
Vicki               en_US    # Isn't it nice to have a computer that will talk to you?
Victoria            en_US    # Isn't it nice to have a computer that will talk to you?
Whisper             en_US    # Pssssst, hey you, Yeah you, Who do ya think I'm talking to, the mouse?
Zarvox              en_US    # That looks like a peaceful planet.

Easy Way to Create Colorful Bash Prompt

The Problem

I often want to fiddle with the bash prompt, but don’t want to deal with bash prompt escape sequences. I wish for a utility which simplify setting a bash prompt. I finally wrote that utility myself: mkprompt

Install

Copy mkprompt to a directory in the path.

Using mkprompt

The best way to show mkprompt usage is via a couple of examples.

PS1=$(mkprompt "red workdir" space dollar)  
PS1=$(mkprompt "cyan Workdir" space "green dollar")  
mkprompt # display help

For more information, see my shell_tools page.

What’s Next?

The following are improvements which I plan for mkprompt, depends on my free time:

  • Implement the rest of the prompt escape sequences
  • Improve the help output
  • Implement installation script

The Script

I current host my script as part of my shell_tools collection on GitHub.

Automatically List a Directory’s Contents After Changing Dir

The Problem

After the cd command, the next command is almost always ls so we want to combine the two to automatically issuing the ls command right after the cd command.

The Solution

In bash, add the following line in either ~/.bash_profile or ~/.bashrc:

function cd() { builtin cd "${@:-$HOME}" && ls -l; }

If you are using csh or tcsh, add the following line to .cshrc:

alias cd 'cd \!*; ls -l'

Now, whenever we type a cd command, not only we are changing the work directory, but also list the files at the new location. I would like to thank Matt Jenkins for helping me out with the csh part.

Bash – Log to Screen and File Simultaneously

The Problem

In my bash script, I would like to print both the the standard output (typically the screen) and a file.

The Solution

Below is a simple script which demonstrate the solution.

#!/bin/sh

# whatis: Demo script that prints to both screen and a file

{
    echo Logging demo
    echo Output will go both the screen and logging.log
    # Other lines which might produce output here
    
} | tee logging.log

Discussion

Normally, the output from a bash script goes to the standard output, typically the screen. To redirect the output to both the standard output and a file, we employ the tee program. By surrounding the block of code with curly braces, we redirect the whole block, not just individual lines.

Restore Your SSH Session Working Directory

The Problem

I want to login to a remote Linux machine via SSH and to be in the same directory I was before my last log out.

The Solution

Since my login shell is bash, I present this solution in bash, but you can adapt it for your favorite shells. This tip relies on the two special files ~/.bash_profile and ~/.bash_logout. When a user logs out of a Linux system, the login shell (bash in this case) executes the ~/.bash_logout file, which is where we save our working directory:

# Contents of .bash_logout
rm -f $LASTDIRFILE
echo cd $PWD > $LASTDIRFILE

Likewise, we a user logs in, bash execute .bash_profile, so we put the instruction to restore the working directory there:

# Contents of .bash_profile
# ...

# Restore last directory
export LASTDIRFILE=~/.lastdir
test -f $LASTDIRFILE && source $LASTDIRFILE

Conclusion

The ~/.bash_logout is a wonderful file for saving your session’s details and its counterpart, ~/.bash_profile, is good for restoring them.

Adding Confirmation to bash

The Problem

Bash does not have a “confirm” command to solicit the user’s confirmation of an action. I realize that some commands, such as rm which can ask for the user’s confirmation via the -i flag, but many do not. In addition, when writing bash scripts, I often run into situations which requires the confirmation for a series of commands, not just a single one.

The Solution

I created a confirm command, really a bash function which we can include in our script. The interface for this function is simple: think of it as a stripped-down version of the echo command. Below is the contents of confirm.sh, where I defined the confirm function.

The Code

# ======================================================================
#
# Function: confirm
# Asks the user to confirm an action, If the user does not answer yes,
# then the script will immediately exit.
#
# Parameters:
# $@ - The confirmation message
#
# Examples:
# >  # Example 1
# >  # The preferred way to use confirm
# >  confirm Delete file1? && echo rm file1
# >  
# >  # Example 2
# >  # Use the $? variable to examine confirm's return value
# >  confirm Delete file2?
# >  if [ $? -eq 0 ]
# >  then
# >      echo Another file deleted
# >  fi
# >  
# >  # Example 3
# >  # Tell bash to exit right away if any command returns a non-zero code
# >  set -o errexit
# >  confirm Do you want to run the rest of the script?
# >  echo Here is the rest of the script
#
# ======================================================================

function confirm()
{
    echo -n "$@ "
    read -e answer
    for response in y Y yes YES Yes Sure sure SURE OK ok Ok
    do
        if [ "_$answer" == "_$response" ]
        then
            return 0
        fi
    done

    # Any answer other than the list above is considerred a "no" answer
    return 1
}

Discussion

To use the function, just save the contents of the file above and name it
confirm.sh. Before using the confirm command
in your script, include the confirm.sh:

source confirm.sh

The examples above should provide enough information to get started. For comments,
suggestions, bugs report, please post a comment to this blog post.

Sweeten Bash History by Adding Grep

The Problem

While I know about the Ctrl-R key combination in bash to perform an incremental reverse search the history; I often need to grep the history to find what I want. For example, to find out what directory I changed into, I issue the following command:

$ history | grep cd

That’s a lot of typing for a lazy guy like me. Imagine that. I rather spend my time writing this blog that repeating that command.

The Solution

To solve this problem, I created a simple function and placed it in my ~/.bash_profile file:

function h() {
    if [ -z "$1" ]
    then
        history
    else
        history | grep "$@"
    fi
}

Explanation

  • Line 2-5: If the user call the command h without any parameter, the function calls the history command
  • Line 6-7: Otherwise, issue the history command and use grep to search.

Going back to my original example, the command now becomes:

$ h cd

Clearly, this is the way life should be: short and sweet. See you in another post.

Quick and Dirty Way to Parse Command Line in a Bash Script

The Problem

I want a quick and dirty way to parse command line from my bash script. For example:

	myscript.sh --file foo.txt --width 72

The Solution

The method is truly quick and dirty, but before we dive right in, let’s make a few assumptions:

  • Each flag must be followed by a value. That means –debug 1 is fine, but –debug is not
  • The flag name will become the variable name. For example, –file foo.txt will result in a variable $file which has value foo.txt
  • The function does not check or validate the variables in any way. It’s a GIGO (garbage in, garbage out) situation
  • Flags can have one- or two-dash: -debug is the same as –debug

With that in mind, let’s take a look at the function

# file: getopt_simple.sh
function getopt_simple()
{
    until [ $# -eq 0 ]
    do
        eval ${1##*-}='$2'
        shift 2
    done
}

Below is a sample script which make use of this function

#!/bin/sh

# file: getopt_simple_tryout
# Try out the simple getopt function

# Include the function
source getopt_simple.sh

# Simulate the command line
set -- --file myfile.txt -depth 3 -width 72 --name "Hai Vu"

# Parse the command line
getopt_simple "$@"

# Now show the variables
echo "file  = $file "
echo "depth = $depth"
echo "name  = $name "

Here is its output:

	$ getopt_simple_tryout.sh 
	file  = myfile.txt 
	depth = 3
	name  = Hai Vu 

Explanation

  • Line 4: Loop until we exhaust the parameters on the command line
  • Line 6: The ${1##*-} expression strips the preceding dashes (-) from $1. We treats $1 as the name of the variable and $2 as its value
  • Line 7: Move to the next pair of parameters

Conclusion

This function does not do any checking or validation at all, but it is short and sweet–good for those times when you need to try something out quickly

Simple Menu with Bash’s Select Command

The Problem

Sometimes, I need a simple menu in bash, but I don’t want to spend a good deal of time coding for one.

The Solution

Bash has a built-in command called select which gets the job done. To demonstrate this command, I am going to write a short bash script. This script lists all the files in the current directory, then prompts the user to make a choice. If the choice is valid, it invokes the editor on the file. It will ignore any invalid choice. Below is the source for that script:

#!/bin/sh
# Displays a list of files in current directory and prompt for which
# file to edit

# Set the prompt for the select command
PS3="Type a number or 'q' to quit: "

# Create a list of files to display
fileList=$(find . -maxdepth 1 -type f)

# Show a menu and ask for input. If the user entered a valid choice,
# then invoke the editor on that file
select fileName in $fileList; do
	if [ -n "$fileName" ]; then
		vim ${fileName}
	fi
	break
done

Explanation

Line 6 – By default the select commmand uses ‘#?’ as a menu prompt. If the variable PS3 is defined, it will use that variable instead.

Line 9 – The find command retrieves a list of files in the current directory. The script then stores the result in the variable fileList

Line 13 to 18 – The select .. do .. done command displays a menu using $fileList as a list of items.

Line 14 to 16 – This block of code check if the user’s choice was valid and invoke the editor accordingly

Line 17 – The select command acts like an endless loop unless a break command is encountered

Sample Run

Below is a sample run

	$ edit_files.sh
	1) ./arguments		        
	2) ./data		            
	3) ./for_example	        
	4) ./getopt_function.sh	   
	5) ./getopt_homemade.sh	   
	6) ./getopt_tryout
	Type a number or 'q' to quit: 3

The Hack

If you are reading this far, I hope you detected my hack (hint, look at the PS3 prompt). By experimenting with select, I found out that if the user entered an invalid choice (i.e. a letter ‘q’ instead of an integer) then select will set the control variable ($fileName in this case) to empty. Taking advantage of this feature (or bug?), I designed the prompt and check for non-empty variable (line 14-16) before invoking the editor.

Conclusion

The select command is easy to use, but it save the script writers from the tedious job of displaying the menu, the prompt, then ask for the user’s input. The only caveat programmers need to watch out for is the lack of input validation so be sure to check your control variable before using it.