Bash – Array Primer

Posted mostly for my own reference since I always forget the syntax, arrays are invaluable when writing moderately complex shell scripts. If you're writing any serious shell scripts you'll want to refer The Linux Documentation Project's excellent primers: Bash Guide for Beginners and Advanced Bash-Scripting Guide

array.sh

#!/bin/bash
 
echo "<<< Load a file into an array"
echo "# Set the IFS (Internal Field Separator) to a newline
IFS='
'
# Load file.txt from the current directory
arr=( \$( < file.txt ) )
"
 
 
IFS='
'
arr=( $( < file.txt ) )
 
 
echo "<<< Addressing individual array elements"
echo "\${arr[0]} = ${arr[0]}" # the first line of the file
echo "\${arr[1]} = ${arr[1]}" # the second line of the file
 
 
echo ""
echo "<<< \${#VARNAME[@]} will always return the number of elements in an array"
echo "\$arr contains ${#arr[@]} (\${#arr[@]}) items"
 
echo ""
echo "<<< Loop through the array (\${arr[@]}), loading each item as \$foo."
num=1
for foo in "${arr[@]}" ; do
	echo "Loop iteration $num: $foo"
	num=$((num+1))
done
 
 
echo ""
echo "<<< Loop through the array, addressing each item with an index"
num=0
while [[ $num -lt ${#arr[@]} ]] ; do
	echo "Array index $num (\${arr[$num]}): ${arr[$num]}"
	num=$((num+1))
done

file.txt

file line 1
file line 2
file line 3
file line 4

Saving the two files above as array.sh and file.txt, and running array.sh yields:

$./array.sh 
<<< Load a file into an array
# Set the IFS (Internal Field Separator) to a newline
IFS='
'
# Load file.txt from the current directory
arr=( $( < file.txt ) )
 
<<< Addressing individual array elements
${arr[0]} = file line 1
${arr[1]} = file line 2
 
<<< ${#VARNAME[@]} will always return the number of elements in an array
$arr contains 4 (${#arr[@]}) items
 
<<< Loop through the array (${arr[@]}), loading each item as $foo.
Loop iteration 1: file line 1
Loop iteration 2: file line 2
Loop iteration 3: file line 3
Loop iteration 4: file line 4
 
<<< Loop through the array, addressing each item with an index
Array index 0 (${arr[0]}): file line 1
Array index 1 (${arr[1]}): file line 2
Array index 2 (${arr[2]}): file line 3
Array index 3 (${arr[3]}): file line 4

apr_sockaddr_info_get() failed for example.com+

I've had an issue on a server for a little while now where Apache works fine, but apachectl configtest throws a warning.

# apachectl configtest
httpd: apr_sockaddr_info_get() failed for foo.example.com+
httpd: Could not reliably determine the server's fully qualified domain name, using 127.0.0.1 for ServerName

Everything worked, so I never dug too deep, but each time I saw the message I got a little more annoyed. I thought there might be a hidden character in my httpd.conf file, but the actual issue wasn't related to Apache at all – it was the machine's hostname. I never noticed it because with a machine named foo.example.com I'd only see username@foo at a stock bash command prompt.

Running hostname showed that the the hostname had a trailing +

# hostname
foo.example.com+

It was easily fixed by running:

# hostname foo.example.com

Validate Symlinks on Mac OS X

I'm working on an Folder Action to automatically create some symlinks, and needed to account for a finite number of temporary files, which requires that I know if a symlink is valid or not.

Apple's Developer Tools ships with a utility called GetFileInfo that does just the trick.

/Developer/Tools/GetFileInfo -aa _SYMLINK_ >/dev/null 2>&1 ; echo $?

This will return 0 if the symlink (_SYMLINK_) points to a valid file, and as near as I can tell, 3 if the file does not exist.

Bonus

On Linux you can find the filenames of all broken symlinks in a given directory using find/xargs/grep/sed with

find . -type l -print0 | xargs -0 file | grep "broken symbolic" | sed -e 's/^\|: *broken symbolic.*$/"/g'

The only piece that doesn't work on OS X is the sed regexp (which I'm too lazy to fix), but you can kind of work around that with cut, as long as your symlink filenames don't contain a colon.

find . -type l -print0 | xargs -0 file | grep "broken symbolic" | cut -d':' -f1

mount or unmount volumes as a non-root user

I recently needed to be able to mount volumes on a Linux server from a script and I ran into the "mount: only root can do that" error, even with an intermediate SUID script (because I believe mount checks the real user id, not just the effective one). The device and mount point are not consistent so adding an entry to /etc/fstab wasn't an option, nor was passing a password to sudo.

Enter the /etc/sudoers file.  By default sudo requires that a user provide their password, but you can use the NOPASSWD option to bypass this requirement.  This was perfect.

To allow the user corey to run /bin/mount and /bin/umount on all machines without a password add the following line to /etc/sudoers:

corey  ALL=NOPASSWD: /bin/mount, /bin/umount

To allow all members of the group 'wheel' to run /bin/mount and /bin/umount on all machines without a password add the following line to /etc/sudoers:

%wheel  ALL=NOPASSWD: /bin/mount, /bin/umount

Now one of the privileged users can run:

sudo mount /dev/sdb1 /some/path/to/mountpoint

And mount without issue – or a password prompt.


© 2007-2012, Corey Gilmore | Posts RSS Feed | Comments RSS Feed | Contact

 

The views expressed on these pages are mine alone and not those of any past or present employer. All information presented on this site was obtained lawfully and not through disclosure under the terms of an NDA.