Recently I got a bunch of filenames that looked like this:
Wekker0001.png Wekker0002.png Wekker0003.png WekkerXXXX.png
These files had to be renamed such that numbering started at zero instead of one. You can do this with the following shell script:
for i in *; do # First remove the prefix num=${i#Wekker} # Then remove the extension num=${num%.png} # Bash interprets a leading zero as an octal number; force base 10 num=$((10#$num)) # Now subtract one num=$((num-1)) # Rename the file mv $i Wekker$(printf "%04d" $num).png done
If you want to test whether a file has a certain extension, use the following trick:
# Test for the extension "plist" if [ "${x%%plist}" == "$x" ] then echo "Does not have the extension" else echo "Has the extension" fi
Basically, you try to strip off the extension (in this case, "plist"), and if stripping succeeded, it won't be the same as the original filename.
Sometimes you want to temporarily disable signals like Interrupt (CTRL-C). The solution:
trap '' INT # Disable interruptions # Do whatever you want here which must be uninterruptible sleep 10 trap - INT # Restore normal function of the interrupt signal
To do a cleanup or similar after the user hits CTRL-C:
trap "rm the_temporary_file; exit;" INT TERM sleep 10 # Do lots of nifty stuff here trap - INT TERM
Place test.cgi in a directory which is supposed to be configured to run CGI scripts and test with browser.
function print_error () { echo "Parameter 1: $1" echo "Parameter 2: $2" }
print_error "First param" "Second param"
You can't really return values in a function in bash, however you can echo text and then catch output with backticks.
function return_a_string () { echo "This is the return value" }
RETURN_VALUE=`return_a_string()` echo $RETURN_VALUE
To save a text file, create the following HTML file:
<form action="/cgi-bin/post.sh" method="post" enctype="multipart/form-data"> <input name="file" file"> <input type="submit" value="Submit"> </form>
Then, in your cgi-bin, create a shellscript with the following lines:
#!/bin/sh while read var do echo $var >> outputfile done
echo "Content-type: text/plain" echo "" echo "Thanks!"
Of course, stuff gets pasted by the browser around the text. And this is a security hole to leave it wide open like this. So don't.
To save a binary or text file, create the HTML file like mentioned in the previous example. Then, in your cgi-bin, create a shellscript with the following lines:
boundary=$(export | \ sed '/CONTENT_TYPE/!d;s/^.*dary=//;s/.$//') filename=$(echo "$QUERY_STRING" | \ sed -n '2!d;s/\(.*filename=\"\)\(.*\)\".*$/\2/;p') file=$(echo "$QUERY_STRING" | \ sed -n "1,/$boundary/p" | sed '1,4d;$d')
The uploaded file is now contained in the $file variable.
Debugging is easiest done by writing to stderr. When the script is run through CGI, this ends up in the web server's error log:
echo "This is a debugging message" >&2
Before printing the Content-type, set a cookie with:
value="some value" echo Set-Cookie: name=$value
SERVER_SOFTWARE = Apache/2.0.52 (Fedora) SERVER_NAME = localhost SERVER_PORT = 80 REQUEST_METHOD = GET SCRIPT_NAME = /~b.kuik/cgi-bin/test.cgi QUERY_STRING = name=value1&name2=value2 REMOTE_HOST = REMOTE_ADDR = 127.0.0.1 REMOTE_USER = AUTH_TYPE = CONTENT_TYPE = CONTENT_LENGTH =
To parse parameters and cookies, include Bashlib like this:
. ./bashlib
Then, read cookies and parameters like this:
sender=`param sender` recipient=`cookie recipient`
And test whether they're actually passed with:
if [ -z $sender ]; then echo "Sender param not passed!" >&2 fi
The HTML looks like this :
<input type="submit" value="Save" name="button1">
Then test like this:
button1=`param button1` if [ "x$default" != "x" ]; then # do the default fi
Problem: you want to start a process in the background, but want your CGI script to finish:
#!/bin/sh process_that_takes_a_long_time >/dev/null 2>&1 & echo Content-type: text/plain echo echo Finished!
This doesn't work. The reason is that the started process keeps its stdout and stderr open, it's just redirected to the null device. Use the following syntax to close them:
process_that_takes_a_long_time >&- 2>&- &
Your script will immediately finish.
trap "stty echo ; exit" 1 2 15 stty -echo read password stty echo trap "" 1 2 15
If the user press Ctrl+C in the password prompt, the normal stty mode will be restored
To run a single command as another user in a script run by root:
su mysql -c mysql_install_db
If you want to run multiple commands, a HERE document is very useful:
su - mysql <<HERE execute some commands as user HERE
Be careful though; environment variables are taken from the environment outside of the su statement, not the user that was switched to. Take, for example, the following script:
#!/bin/sh su - nobody << HERE echo "User is: $USER" echo "Output of id: ${ID}" HERE
When running as root, this will print:
User is: root
Backticks also seem problematic, better not use them inside the su HERE block.
Sometimes, you need to put a password in a shell script. There are several methods to hide passwords from the unintentional glance by those who you trust. And you need to trust them, since the password can easily be recovered.
$ echo secret_password | rot13 frperg_cnffjbeq
In the shell script, do something like:
PASSWORD=`echo frperg_cnffjbeq | rot13` # Colleagues, please don't look at this password
To bring up a dialog box through a shellscript in X, use the xdialog package:
Xdialog --msgbox "Don't forget your coffee" 10 50
The safest method is to use mktemp:
TMPFILE=`mktemp` echo "Very important data" > $TMPFILE
The mktemp command creates a unique file in /tmp and prints the name. The backticks catch the name and put it in the TMPFILE variable.
If you need a directory, add the -d option.
If you need a prefix, pass a template where the row of capital X'en is replaced by a unique string:
TMPFILE=`mktemp -t hello_XXXXXXXXXX` || exit 1 echo "Very important data" > $TMPFILE
# If we're logging in through SSH, write this down if [ -n "$SSH_CLIENT" ]; then LOGFILE=".ssh/.mylog" # The variable SSH_CONNECTION has the form # FROM_IP FROM_PORT TO_IP TO_PORT if [ -e $LOGFILE ]; then echo "`date`: SSH_CONNECTION $SSH_CONNECTION" >> $LOGFILE else echo "`date`: SSH_CONNECTION $SSH_CONNECTION" > $LOGFILE fi
# Alternative settings SRON0311="172.16.140.14" FROM=`echo $SSH_CLIENT | cut -f1 -d" "` case $FROM in *$SRON0311) export TMOUT=180 #Logout after 3 minutes ;; esac fi
The other substring operator is "#" which removes prefix patterns. If you think about it, "#" is used before a number, e.g. #6, and "%" appears afterwards, e.g. "6%". This will help keep it clear which one removes prefixes and which suffixes.
The other thing to note about these operators is that a _single_ # or % means match the shortest substring and that doubling the operator means match the longest substring. i.e. "%%" and "##".
These let you avoid a lot of external programs in shell scripts. e.g. dirname(1) and basename(1) can be more effeciently done within sh as "${file%/*} and "${file##*/}.
FYI, here's the chunk from the "Paremter Expansion" section of the Bourne shell sh(1) manpage. Note the below are all part of the Single Unix Specification standard and have been for years.
${parameter%word} Remove Smallest Suffix Pattern. The word is expanded to produce a pattern. The parameter expansion then results in parameter, with the smallest portion of the suffix matched by the pattern deleted.
${parameter%%word} Remove Largest Suffix Pattern. The word is expanded to produce a pattern. The parameter expansion then results in parameter, with the largest portion of the suffix matched by the pattern deleted.
${parameter#word} Remove Smallest Prefix Pattern. The word is expanded to produce a pattern. The parameter expansion then results in parameter, with the smallest portion of the prefix matched by the pattern deleted.
${parameter##word} Remove Largest Prefix Pattern. The word is expanded to produce a pattern. The parameter expansion then results in parameter, with the largest portion of the prefix matched by the pattern deleted.
-- From a post by Adrian Filipi
Some examples...
To print the current directory name without the leading path:
$ echo ${PWD##*/}
In a script, to print the script name:
echo ${0##*/}
To remove the extension from a filename:
FILENAME="some_package.tar.gz" echo ${FILENAME%.tar.gz}
The following bash function can be included in your scripts to provide feedback to the user whenever an action (such as starting a process) takes time. Instead of the while [ true ], you should put a condition there which can be periodically checked.
function bounce() { dot=1 while [ true ]; do sleep 0.5 if [ $dot -eq 0 ]; then echo -n $'\b' echo -n "." dot=1 else echo -n $'\b' echo -n " " dot=0 fi done }