IC221: Systems Programming (SP14)


Home Policy Calendar Syllabus Resources Piazza

Lab 02: Basic Bash Scripting (SOLUTIONS)

Table of Contents

Task 1

The file /etc/passwd contains all the login information (not passwords) for users on the system. Each line looks a little like this:

username     groupid            home dir
 |             |                      |
 v             v                      v
aviv:x:35001:10120:Adam Aviv {}:/home/scs/aviv:/bin/bash
        ^               ^                         ^
        |               |                         |
       uid             name                    default shell

Write a script, allusers.sh, that will parse the /etc/passwd file and print a list of all the usernames.

Place your allusers.sh script in your scripts folder

Solution

#!/bin/bash

cut -d ":" -f 1 /etc/passwd

Task 2

Write a script, getname.sh that takes a username as an argument and prints the full name of that use. Here is some sample output.

#> ./getname.sh aviv
Adam Aviv {}
#> ./getname.sh m159999
Midn Midshipmen Class 15 Test {}

Because you want to match precise usernames, you can use the following grep regular expression in your script:

grep "^USERNAME:"

Where USERNAME is replaced by the username you are searching for. If the user is not found, your script can print nothing.

For additional tests, you can compare your program to the finger command which does something similar. See the manual for more detail.

Solution

#!/bin/bash

grep "^$1:" /etc/passwd | cut -d ":" -f 5

Task 3

Write a script, getsize.sh, which takes a path as an argument and prints out the size of the file/dir at that path. Your script must do error checking. Here is some sample output:

#>./getsize.sh
ERROR: Require file
#>./getsize.sh adadf
ERROR: File adadf does not exist    
#>./getsize.sh empty.txt
0
#>./getsize.sh larger.txt
5000

You should be able to use a cut, ls, and/or wc to get the information you need. All errors should be written to stderr such that:

#> ./getsize.sh badfile > /dev/null
ERROR: File badfile does not exist    

You may also find it useful to use the tr command. Consult the man page for more details, but tr is used for translation, substituting one string for another. The squeeze option -s, in particular, could be useful to get rid of extra whitespace so that your cut fields are more consistent.

Solution

#!/bin/bash# 

# getsize.sh path
#
# Print the path

if [ $# -lt 1 ]
then
    echo "getsize.sh: ERROR: Require file" 1>&2

elif [ ! -e $1 ]
then
    echo "getsize.sh: ERROR: File $1 does not exist" 1>&2

else
    ls -l $1 | tr -s ' ' | cut -d " " -f 5
fi

Task 4

Create a script called getallsizes.sh, which takes in any number of files on the command line and prints their sizes as follows. Here's some sample usage:

#> ./getallsizes.sh empty.txt isbigger.sh larger.txt 
empty.txt 0
isbigger.sh 204
larger.txt 5000
#> ./getallsizes.sh empty.txt BADFILE larger.txt 
empty.txt 0
getallsizes.sh: ERROR: File BADFILE does not exist
larger.txt 5000
#> ./getallsizes.sh empty.txt BADFILE larger.txt 2>/dev/null 
empty.txt 0
larger.txt 5000

(HINT) Check out the man page for echo to print without a trailing new line so you can align the file name with their size.

Solution

#!/bin/bash# 

# getallsizes.sh [path [path [..]]]
#
# Print the size of all the files in the path

for f in $*
do
    if [ ! -e $f ]
    then
        echo "getallsizes.sh: ERROR: File $f does not exist" 1>&2
    else
        echo -n "$f " 
        ls -l $f | tr -s ' ' | cut -d " " -f 5
    fi
done

Task 5

Write a script, isbiggerthan.sh, which takes a path and a size and determines if the file or directory is bigger (or equal to) the given size. Here is the usage:

isbiggerthan size path

And here is some sample output

#> ./isbiggerthan.sh
isbiggerthan.sh: ERROR: Require path and size 
#> ./isbiggerthan.sh 1
isbiggerthan.sh: ERROR: Require path and size 
#> ./isbiggerthan.sh 0 empty.txt 
yes
#> ./isbiggerthan.sh 2 empty.txt
no
#> ./isbiggerthan.sh -1 empty.txt
isbiggerthan.sh: ERROR: Require a positive number for -1
#> ./isbiggerthan.sh ad empty.txt
isbiggerthan.sh: ERROR: Require a number for ad
#> ./isbiggerthan.sh 0 doesnotexist
isbiggerthan.sh: ERROR: File doesnotexist does not exist
#> ./isbiggerthan.sh 3000 larger.txt 
yes
#> ./isbiggerthan.sh 10000 larger.txt 
no

Note, checking if a variable is a number is non trivial. Here is some simple code to do that:

if [ "$var" -eq "$var" ] 2> /dev/null # check for a number pipe error to /dev/null
then
    echo "it's a number"
else
    echo "it's *not* a number"
fi

Solution

#!/bin/bash# 

# isbiggerthan.sh size path
#
# Print the path

if [ $# -lt 2 ]
then
    echo "isbiggerthan.sh: ERROR: Require path and size " 1>&2
    exit 2
fi

if [ "$1" -eq "$1" ] 2> /dev/null # check for a number
then
    echo -n #it's a number
else
    echo "isbiggerthan.sh: ERROR: Require a number for $1" 1>&2
    exit 3
fi


if [ $1 -lt 0 ]
then
    echo "isbiggerthan.sh: ERROR: Require a positive number for $1" 1>&2
    exit 4
fi

if [ ! -e $2 ]
then
    echo "isbiggerthan.sh: ERROR: File $2 does not exist" 1>&2
    exit 5
fi




size=$(ls -l $2 | tr -s ' ' | cut -d " " -f 5)

if [ $size -ge $1 ]
then
    echo "yes"
    exit 0
else
    echo "no"
    exit 1
fi

Task 6

Update your isbiggerthan.sh script to exit with different status codes dependent on if the file is bigger than the size or if there is an error. For example:

  • exit 0 : if the file is bigger (or equal) to the size
  • exit 1 : if the file is not bigger (or equal) to the size
  • exit 2 : if not enough arguments
  • exit 3 : did not receive a number for size
  • exit 4 : recieved a negative number for size
  • exit 5 : file does not exist

Once complete, now create a new script isbiggerthanall.sh which takes the following arguments:

isbiggerthanall.sh size path [path [...]]

which outputs all the files provided that are bigger than the size. Your script must call your isbiggerthen.sh script and check the exit status to determine the result and output.

Here is some sample output:

isbiggerthan size path

And here is some sample output

#> ./isbiggerthanall.sh 1
isbiggerthanall.sh: ERROR: Require a size and at least one file
#> ./isbiggerthanall.sh 1 empty.txt
#> ./isbiggerthanall.sh 0 empty.txt
empty.txt
#> ./isbiggerthanall.sh 0 larger.txt
larger.txt
#> ./isbiggerthanall.sh 6000 larger.txt empty.txt
#> ./isbiggerthanall.sh 0 larger.txt empty.txt
larger.txt
empty.txt
#> ./isbiggerthanall.sh 0 *
empty.txt
emtpy.txt
getallsizes.sh
getsize.sh
isbigger.sh
isbiggerthan.sh
isbiggerthanall.sh
larger.txt
#> ls *.sh  | xargs ./isbiggerthanall.sh 100
getallsizes.sh
getsize.sh
isbigger.sh
isbiggerthan.sh
isbiggerthanall.sh
#> ls *.sh  | xargs ./isbiggerthanall.sh 300
isbiggerthan.sh
isbiggerthanall.sh

(On Your Own) Google and learn about case statements for bash, and use a case statement to check the exit condition.

Solution

#!/bin/bash

# isbiggerthan.sh size [path [path [...]]]
#
# print the files that are bigger than the size

if [ $# -lt 2 ]
then
    echo "isbiggerthanall.sh: ERROR: Require a size and at least one file" 1>&2
    exit 2
fi

i=1
for f in $*
do
    if [ $i -ne 1 ]  #not for first argument which is the size
    then
        ./isbiggerthan.sh $1 $f 2>&1 > /dev/null
        case $? in
            0)
                echo $f ;;
            1)
                echo -n ;;
            2) 
                echo "isbiggerthanall.sh: ERROR: isbiggerthan.sh" 1>&2 ;;
            3)
                echo "isbiggerthan.sh: ERROR: Require a number for size not $1 " 1>&2 ;;
            4)
                echo "isbiggerthan.sh: ERROR: Require a positive number " 1>&2 ;;
            5)
                echo "isbiggerthan.sh: ERROR: File $f does not exist" 1>&2 ;;
            *)
                echo "HUH? $?" ;;
        esac
    fi

    let i++
done