This article is a cheat sheet for things you learn to do in Bash (The Bourne Again Shell). Also, see the page of [[one-liners]]
== For Loops ==
The bash one-liner for doing a for loop looks something like this:
<source lang="bash">
for i in `seq 1 10`; do echo "$i, "; done
</source>
== Arrays ==
In Bash v4 you can use multi-dimensional array but if you might want to use a different language. See http://wiki.bash-hackers.org/syntax/arrays for more.
Simple arrays work like the following:
<source lang="bash">
#!/bin/bash
# declare three arrays
# you can use the 'declare' built-in, but don't have to
# declare -a breakfast=('eggs' 'pancakes' 'cereal')
breakfast=('eggs' 'pancakes' 'cereal')
lunch=('sandwich' 'salad' 'smoothie')
dinner=('pasta' 'stir-fry' 'burritos')
# Now how do we loop through them?
# We could just output the values for breakfast
for i in "${breakfast[@]}"
# for BASH 3+ we have ! to access the numeric index
for i in "${!breakfast[@]}"
# Or, Build a sequence to loop through the other arrays by index
# while using a counter that is more friendly
# Arrays are indexed using integers and are zero-based
# the index number (starting at zero) gives that element
for (( i=1; i<${#breakfast}; i++ ))
do
echo "Here is the menu for day ${i}"
echo "breakfast: ${breakfast[$i-1]}"
echo "lunch: ${lunch[$i-1]}"
echo "dinner: ${dinner[$i-1]}"
done
declare -a fruits=('apples' 'oranges' 'banannas')
declare -a vegetables=('broccoli' 'onions' 'peppers' 'potatoes' 'carrots')
# the # character gives us the count of the array
echo "we have ${#fruits[@]} fruits available"
printf "%s\n" "${fruits[@]}"
echo "we have ${#vegetables[@]} vegetables available"
printf "%s\n" "${vegetables[@]}"
</source>
== If construct ==
The <code>then</code> can go on the same line as the <code>if</code> as long as you use a semi-colon to terminate the if clause. Alternately, you can put the <code>then</code> on it's own line
<source lang="bash">
if EXPR; then
# do stuff
fi
</source>
is equivalent to
<source lang="bash">
if EXPR
then
# do stuff
fi
</source>
Adding an <code>else</code> clause
<source lang="bash">
if EXPR; then
# do stuff
else
# do other stuff
fi
</source>
Adding multiple <code>else</code> clauses with <code>elif; then</code>
<source lang="bash">
if EXPR; then
# do stuff
elif EXPR; then
# do other stuff
else
# final else
fi
</source>
Note: sometimes you want to comment out a section of an if/else block, or maybe it does nothing at all. In this case, you'll get an error. To avoid the error, you can use the [http://www.gnu.org/software/bash/manual/html_node/Bourne-Shell-Builtins.html bash built-in] <code>:</code> (colon command)
<pre>
: [arguments]
</pre>
Do nothing beyond expanding arguments and performing redirections. The return status is zero.
<source lang="bash">
if [ -f "/tmp/Non-existing-file.txt" ] ; then
echo "I found the non-existing file"
else
: # the colon command prevents an error if there are no other statements in this block
fi
</source>
== Using Find ==
The find command in Linux is very powerful, and thus somewhat complex to learn all the syntax and options.
Suffice to say that you can read the manpage and info pages to answer your questions. === Mime report ===This example finds and counts files by their extension. However, A "poor-man's mime-report"<source lang="bash" line>for x in \ $(find . -maxdepth 1 -type d | \ sort | \ grep -v ^./$ ); \do \ echo -e "\n\n$x\n"; \ find "$x" -type f | \ egrep -o '\.(.?.?..)$' | \ sort | \ uniq -c ; \done</source>Breakdown:<pre> 1. stuff everything up to the semicolon into a loop variable named '$x' 2. look for directories that are immediate descendants of the current directory 3. sort them 4. don't include the current directory 6. with these: echo the name of the directory as a 'heading' in the report 7. find the files per directory 8. match only on the extensions found (between two and four-letter extensions) 9. sort them10. count and summarize by unique values</pre> But it looks more impressive as a one-liner:<source lang="bash">for x in $(find . -maxdepth 1 -type d|sort|grep -v ^./$); do echo -e "\n\n$x\n"; find "$x" -type f | egrep -o '\.(.?.?..)$' | sort | uniq -c ; done</source> === Prune ===In case you are trying to figure out the prune option so that you can efficiently scan a directory for something while also ignoring .svn metadata, here is an example:
<source lang="bash">
find ./ -name .svn -prune -o -name "*html*"
arg_count=$#
for (( i=1; i <= $arg_count; i++ )); do
arg="$1" shift 1 if [ -z "$pattern" ]; then if [ "$arg" == "--" ]; then grepargs="$grepargs $arg" pattern="$1" shift 1 ((i++)) elif [ "${arg:0:1}" != "-" ]; then pattern="$arg" else grepargs="$grepargs $arg" fi else pathargs="$pathargs $arg" fi
done
echo "Finished fixing websites"
echo
</source>
Sometimes when using find, you end up with "Permission denied" errors that add noise to your output. There are a couple solutions for this. Use the '''prune''' option to skip entire trees that you should avoid (e.g. /proc). Use shell redirection to ignore remaining error messages (e.g. 2>/dev/null).
The following example searches all of my hard drive starting at / but skips over the backups directory I have in my external disk drive and also skips over the "process" directory. Any errors like files in /var that I do not have permission to see are discarded by redirecting STDERR to the bitbucket.
<source lang="bash">
find / -path /media/disk/backups -prune -o -path /proc -prune -o -type d -name soffice.cfg 2>/dev/null
/home/greg/.openoffice.org2/user/config/soffice.cfg
/home/greg/.openoffice/1.1.1/user/config/soffice.cfg
/home/greg/.openoffice.org/3/user/config/soffice.cfg
/home/greg/spidey2/.openoffice.org2/user/config/soffice.cfg
/home/greg/liberty/greg/.openoffice.org2/user/config/soffice.cfg
/home/greg/liberty/greg/.openoffice/1.1.1/user/config/soffice.cfg
/opt/openoffice.org/basis3.0/share/config/soffice.cfg
/usr/lib/openoffice/basis3.0/share/config/soffice.cfg
/proc
/media/disk/backups
</source>
=== Move a directory up one level ===
Sometimes you can end up with a directory which is nested inside it's intended destination. For example, <code>drush archive-restore (arr)</code> can leave you with <tt>/var/www/drush/drush</tt> and you want the contents of the sub-directory to be at the location of it's parent. Using the BASH shell options for glob control, you can set dotglob and later unset it to be able to move * up. This worked for me on one host, and didn't work on another. For the one that didn't work, mv kept complaining that destination directories were not empty. I don't care if the destination directories exist.... that's the whole point. Uggh!
<source lang="bash">
cd /var/www/drupal/drupal/
shopt -s dotglob
mv -- * ..
shopt -u dotglob
</source>
==Resources==
* greycat ([http://wooledge.org/~greg/ Greg Wooledge]) Wiki http://mywiki.wooledge.org/EnglishFrontPage maintained by lhunath (Maarten Billemont)
* http://bash.cyberciti.biz/guide/Main_Page an excellent BASH and Linux wiki site
* http://wiki.bash-hackers.org/scripting/style
* [http://mywiki.wooledge.org/BashGuide Maarten Billemont's Bash Guide]
* [http://www.gnu.org/software/bash/manual/bashref.html Bash Reference Manual]
* [http://penguinpetes.com/b2evo/index.php?title=how_the_one_liner_for_loop_in_bash_goes Penguin Pete]
* [[wp:Bash|Wikipedia page]]
* [https://ss64.com/bash/test.html Bash tests] <code>-f -d -x -w -Z </code> What do all the file test options mean?
[[Category:System Administration]]