Shell Scripting

A cheat sheet for those just starting out. This is the bare minimum, the core mechanics that make it work. A couple things to note before starting:

  • All variables are global
  • All lines are shell commands and are considered piped

Making a script executable

$ chmod +x path/to/script

Variables

  • Datatypes do not need to be declared (at least for strings).
  • Usage of the variable requires $ but declaring does not.
$ foo=bar
$ echo $foo
bar
# multiple variables can be declared on one line
first=foo  middle=bar  last=oop
# use quotes if a string requires spaces
fullname="foo bar oop"
# copy value to another variable
oldname=$fullname

Output

Screen

  • echo
  • printf

File > and >>

Write to file

$ ls ~ > /path/to/file.ext

Append to file

The key difference between > and >> is > requires the target file to exist whereas >> will create the file if it does not exist. I typically just use >> and be done with it.

$ ls ~ > /path/to/file.ext
$ ls ~/Desktop >> /path/to/file.ext

Append a variable’s value to a file

echo $foo >> /path/to/file.ext

Redirection with | (pipe)

$ ls ~ | less

Input

File <

less < /path/to/file.ext

Useful/Common commands

  • echo: Output to terminal
  • tr: search and replace

Process Control

if–elif–else–fi

  • Wrapping a pipeline with brackets—[...]—runs the assessment with test, but this is a particular command with its own syntax, $ man test for more info.
  • Looking for a negative is achieved by placing a ! before the pipeline
if pipeline
# do this
then
# do that
elif pipeline
then
# do these
else
# do those
fi
if [ "$str1" = "$str2" ]
then
... fi

Functions

  • Functions must be declared before their execution. Either by placing the function before the script’s execution code or including it as a separate file.
  • Functions return exit status only (0 for successful) and not calculated/arbitrary values. To capture calculated values use echo.
  • All variables are global!

The following is an example that accepts one argument and returns a value.

# FUNCTION DECLARATION
foo () {
	# set the argument to a "local" variable
	foo_arg=$1
	# do something with foo_arg
	foo_result=$(cmd foo_arg)
	# echo the result so that the caller can use it
	# this is the same thing as returning an arbitrary value
	echo $foo_result
}
# BEGIN SCRIPT EXECUTION
# call the function with an argument and capture the result
bar=$(foo "oop")
# do something with the result
echo $bar

Troubleshooting

Stack trace

Displays in the terminal which command is being run at the time. If the output is directed to a file, trace content is not included in that output.

set -x # turn on tracing
set +x # turn off tracing

Run directly in the prompt with stack tracing

$ sh -x cmd [args]

Example shell script

This script logs the sizes of networks shares and maintains a log between weekly runs. Three functions are declared, emphasizing the global scope of all variables.

# turn on stack trace
set -x

#----------------------
# FUNCTION DECLARATIONS
#----------------------

# PURPOSE: gets the spaced used by a given directory
# INPUT: Path; directory path
# OUTPUT: Integer; space used in MB

get_dir_size() {

	root=$1

	if [ -d $root ]
	then
		# du return path and space used, so go straight to cut
		root_size=$(du -sm $root | cut -f1)
	else
 		echo "$root is an invalid directory"
 		exit
	fi

	echo $root_size
}

# PURPOSE: gets the space available on a share
# INPUT: Path; volume root path
# OUTPUT: Integer; space available in MB

get_free_space() {

	share_free=$1

	if [ -d $share ]
	then
		# df returns a 2 row, 6 column table, so awk is used to get the value we need.
		free_space=$(df -m $share_free | awk 'NR==2' OFS="\t" | awk '{print $4}')
	else
		echo "$share cannot be found"
		exit
	fi

	echo $free_space
}

# PURPOSE: gets the space used on a share
# INPUT: Path; volume root path
# OUTPUT: Integer; space available in MB

get_used_space() {

	share_used=$1

	if [ -d $share ]
	then
		# df returns a 2 row, 6 column table, so awk is used to get the value we need.
		used_space=$(df -m $share_used | awk 'NR==2' OFS="\t" | awk '{print $3}')
	else
		echo "$share cannot be found"
		exit
	fi

	echo $used_space
}

#-------------
# SCRIPT START
#-------------

kLogFile=~/size.txt
# double check the log file's existence to ensure we have a header
if ! [ -e $kLogFile ]
then
	printf "header info" >> kLogFile
fi

now=$(date)
printf  "%s\t" $now  >> $kLogFile # Date

free_mac1=$(get_free_space /Volumes/Mac1)
printf  "%s\t" $free_mac1  >> $kLogFile # Mac1 Free

used_mac1=$(get_used_space /Volumes/Mac1)
printf  "%s\t" $used_mac1  >> $kLogFile # Mac1 Used

# turn off stack trace
set +x

Shell Commands with Cocoa

  • arg can be nil; arg holds the options including target paths
  • Absolute paths must be used for all paths, there is no expansion or redirection, and any customizations made to the shell by the user are not honored here.
    • If the ls command is set to ls -ls as the default, only ls will be run.
    • ~ for the user’s default folder is not expanded to /Users/username; the full path must be used.

This example runs a shell command and handling the result:

NSString *result = [self runCommand:@"/bin/ls" withArguments:[NSArray arrayWithObjects:@"-ls", @"/Users/username", nil]];
NSLog(@"%@", result);

- (NSString *)runCommand:(NSString *)cmd withArguments:(NSArray *)arg {

	// set up the task with the command and arguments including options
	NSTask *task = [[NSTask alloc] init];
	[task setLaunchPath:cmd];
	[task setArguments:arg];

	// set up a pipe to capture the result of the command
	NSPipe *pipe = [NSPipe pipe];
	[task setStandardOutput:pipe];
	NSFileHandle *file = [pipe fileHandleForReading];

	// punch it
	[task launch];

	// read the data and push it back to the caller
	NSData *data = [file readDataToEndOfFile];
	return [[NSString alloc] initWithData:data encoding: NSUTF8StringEncoding];
}