#!/bin/bash -e trap "echo ERROR HANDLING HERE." ERR echo "Starting" asdfasdf echo "End"
And when you run it:
Starting piet.sh: line 5: asdfasdf: command not found ERROR HANDLING HERE. End
Smokes your problems, coughs fresh air.
#!/bin/bash -e trap "echo ERROR HANDLING HERE." ERR echo "Starting" asdfasdf echo "End"
And when you run it:
Starting piet.sh: line 5: asdfasdf: command not found ERROR HANDLING HERE. End
If you want to run a process with root privileges that you can invoke as a less unprivileged user, you can make the program setuid root. This can be very useful, for example, when you want a PHP or CGI script to call a backup process, or to create a new site or irrevocably delete you whole system. The latter example points to a serious security problem: if anyone can figure out a way to make your program do something you don’t want, you’re screwed, because you just gave them root privileges to wreak maximum havoc. That’s why, normally, scripts (anything executed by an interpreter by the kernel because of a shebang) won’t get elevated privileges when you set their setuid bit.
To understand the setuid bit, let’s first see what happens when I try to cat a file that belongs to root:
su - # I am now root; fear me touch no-one-can-touch-me chmod 600 no-one-can-touch-me cat no-one-can-touch-me # cat: Permission denied
Next, I’ll create a shell script that cats the file:
#!/bin/bash
cat no-one-can-touch-me
And make the script setuid root:
su - chown root:root script.sh chmod +xs script.sh
If I now execute the script, I still get the permission denied. What I need to make this work is a wrapper program. For that, I refer to Wiebe’s post about the same subject. (Yeah, I know: why bother publishing this if Wiebe already did an excellent job explaining? Well, I just hate to throw away an otherwise fine draft.)
Here’s how you can remove the appending slash from a path using sed, the stream editor:
/just/a/path/ | sed -e 's#/$##' # Output: /just/a/path # And, if there isn't an appending slash, nothing happens: /just/another/path | sed -e 's#/$##' # Output: /just/another/path
It works quite simple. Sed executes expression (-e) on its standard input. The expression is a substitution using regular expressions. The #-sign is the delimiter. The part (#/) between the first two hash signs is the matching expression and the (empty) part between the second and the third hash sign is the replacement expression. This expression (“s#/$##”) basically says: replace all occurrences of “/” at the end of the line (the dollar sign is the end-of-line anchor) with nothing.
To use this in a script is easy-peasy. Suppose $1 is a system path that may or may not include an appending slash:
#!/bin/bash sanitized_path= "$1" | sed -e 's#/$##'` $sanitized_path
This script outputs its first parameter with the appending slash removed.
#!/bin/bash # # Author: Wiebe Cazemier (wiebe@halfgaar.net) # # Template bash script, for when you need something overengineerd :) # Hack prevention PATH="/sbin:/usr/sbin:/bin:/usr/bin" # Error codes wrong_params=5 interrupted=99 default_error=1 # Function to echo in color. Don't supply color for normal color. echo_color() { message="$1" color="$2" red_begin="\033[01;31m" green_begin="\033[01;32m" yellow_begin="\033[01;33m" color_end="\033[00m" # Set color to normal when there is no color [ ! "$color" ] && color_begin="$color_end" if [ "$color" == "red" ]; then color_begin="$red_begin" fi if [ "$color" == "green" ]; then color_begin="$green_begin" fi if [ "$color" == "yellow" ]; then color_begin="$yellow_begin" fi echo -e "${color_begin}${message}${color_end}" } end() { message="$1" exit_status="$2" if [ -z "$exit_status" ]; then exit_status="0" fi if [ ! "$exit_status" -eq "0" ]; then echo_color "$message" "red" else echo_color "$message" "green" fi if [ "$exit_status" -eq "$wrong_params" ]; then dohelp fi exit $exit_status } # Define function to call when SIGTERM is received trap "end 'Interrupted' $interrupted" 1 2 3 15 dohelp() { echo "" echo "Example script" echo "" echo "help = todo" # Exit because you don't want the script to do anything after displaying help exit } while [ -n "$*" ]; do flag=$1 value=$2 case "$flag" in "--option1") option1=$value shift ;; "--help") dohelp ;; "--") break ;; *) end "unknown option $flag. Type --help" "$wrong_params" ;; esac shift done if [ -z "$option1" ]; then end "option1 not given" $wrong_params fi
Here is a code snippet I use for parameter parsing:
dohelp() { "Example script" "" # Exit because you don't want the script to do anything after # displaying help, and do so with error, so that calling scripts won't think it succeeded 1 } [ -n "$*" ]; flag="$1" value="$2" "$flag" "--one") one="$value" ;; "--two") two="$value" ;; "--pretend") pretend=true ;; "--help") dohelp ;; "--") ;; *) -e "unknown option $flag\n" dohelp ;;
I was trying to install Unreal Tournament GOTY on one of my Linux machines. I downloaded and ran the script ut-install-436-GOTY.run but I got this error:
cannot open `+6' for reading: No such file or directory
This line caused it:
sum1=`tail +6 $0 | cksum | sed -e 's/ /Z/' -e 's/ /Z/' | cut -dZ -f1`
To fix it, I set this environment variable:
export _POSIX2_VERSION=199209
Apparently, this makes programs behave differently. Research is required to find out exactly what it does…
The annoyances that I suffered earlier today during the upgrade of a WordPress plugin made me turn to my favorite text-editor to create a simple script, svn-replace-dir:
#!/bin/bash usage() { cat <<"EOF" $0 [--dry-run] <svn_dir> <replacement_dir> This script replaces the contents of <svn_dir> with the contents of <replacement_dir>, where <replacement_dir> is not an svn directory. Copyleft 2010, Rowan Rodrik van der Molen <rowan@bigsmoke.us> EOF } fatal_error() { message=$1 -e "\e[1;31m$message\e[0m" 1 } usage_error() { error="Wrong usage." [ -n "$1" ]; error=$1 -e "\e[1;31m$error\e[0m" 1 } run_command() { -e "\e[1;34m$1\e[0m" [ $dry_run == 1 ] || $1 } dry_run=0 [ $1 == '--dry-run' ]; dry_run=1 [ $# == 2 ] || usage_error "Wrong number of arguments." svn_dir= "$1"|sed -e 's#/$##'` replacement_dir= "$2"|sed -e 's#/$##'` begin_path=$PWD #if [ "${svn_dir:0:1}" != "/" ]; then svn_dir="$PWD/$svn_dir"; fi #if [ "${replacement_dir:0:1}" != "/" ]; then replacement_dir="$PWD/$replacement_dir"; fi [ -d "$svn_dir" ] || usage_error "$svn_dir is not a directory." [ -d "$replacement_dir" ] || usage_error "$replacement_dir is not a directory." # Create all subdirectories in $svn_dir that do not yet exist $replacement_dir find . -mindepth 1 d -print | sed -e 's#^./##' | d; $begin_path/$svn_dir # Doesn't the destination directory already exist? [ ! -d "$d" ]; run_command "svn mkdir '$d'" # Copy all files from $replacement_dir to $svn_dir $begin_path/$replacement_dir find . f -print | sed -e 's#^./##' | f; $begin_path run_command "cp '$replacement_dir/$f' '$svn_dir/$f'" # FIXME: Quoting problem # Remove all files that do no longer exist in $replacement dir $begin_path/$svn_dir find . f -print | grep -v '.svn' | f; [ ! -f "$begin_path/$replacement_dir/$f" ]; run_command "svn rm '$f'" # Remove all subdirs that do no longer exist in $replacement dir $begin_path/$svn_dir find . -mindepth 1 d -print | grep -v '.svn' | d; [ ! -d "$begin_path/$replacement_dir/$d" ]; run_command "svn rm '$d'" 0
Using the script is simple:
svn-replace-dir simple-tags new-simple-tags|less -R
It replaces all the contents of the first directory (simple-tags
in the example) with those of the second directory and it deletes everything that is no longer present in the second dir. In the process, it does all the necessary calls to svn mkdir, svn rm and (in the next version) svn add.
diff tells me that the script has done its work correctly:
diff -x .svn -ruN simple-tags new-simple-tags
# Emptiness is bliss :-)
This is another one of these occasions when Git would have made life so much easier. Luckily, at least there’s GitHub to host this script as a Gist. Check there if you want to fetch the newest version of this script.
On every machine that I install, I need a custom environment. At the very basic, I need screen and bash customizations. I will attempt to keep this blog post up-to-date with my most recent config.
/etc/bash.bashrc_halfgaar (naming scheme depends on distro):
prompt_command { XTERM_TITLE="\e]2;\u@\H:\w\a" BGJOBS_COLOR="\[\e[1;30m\]" BGJOBS="" [ "$(jobs | head -c1)" ]; BGJOBS=" $BGJOBS_COLOR(bg:\j)"; DOLLAR_COLOR="\[\e[1;32m\]" [[ ${EUID} == 0 ]] ; DOLLAR_COLOR="\[\e[1;31m\]"; DOLLAR="$DOLLAR_COLOR\\\$" USER_COLOR="\[\e[1;32m\]" [[ ${EUID} == 0 ]]; USER_COLOR="\[\e[41;1;32m\]"; PS1="$XTERM_TITLE$USER_COLOR\u\[\e[1;32m\]@\H:\[\e[m\] \[\e[1;34m\]\w\[\e[m\]\n\ $DOLLAR$BGJOBS \[\e[m\]" } PROMPT_COMMAND=prompt_command EDITOR=vim ls='ls --color=auto' ll='ls -l' lh='ls -lh' grep='grep --color=auto'
Don’t forget to source the file in ~/.bashrc
~/.screenrc:
caption always "%{= kB}%-Lw%{=s kB}%50>%n%f* %t %{-}%+Lw%<"
vbell off
startup_message off
term linux
I signed up for a Mollie account so that I can send SMS’s from my machines. To do that, I needed a bash script, so I wrote one:
#! /bin/bash # # Script to send an SMS email notifcation to Mollie's HTTP gateways # Based on the SMS2Email script which can also be found on nagiosexchange.org # Reworked by Dennis Storm - Brainstorm ICT # Again reworked by Wiebe Cazemier (wiebe@halfgaar.net), to include proper failure checks # ################################################################################# config_file="/etc/send-sms.conf" log_file="/var/log/send-sms.log" [ -L "$log_file" ]; "cannot continue, $log_file is a symlink." 2 touch "$log_file" > /dev/null 2>&1 sender="SMSScript" [ -f "$config_file" ]; $config_file curl_location=`which curl 2> /dev/null` [ -z "$curl_location" ]; "Curl command not found. Install that." >&2 1 logfail() { message="$1" message="Failure: [`date`]: $message" $message [ -w "$log_file" ]; "$message" >> "$log_file" 1 } # Show usage if necessary [ $# -eq 0 ]; then "Usage: $0 -s [sender] -n [numbers] -m [message] -u [username] -p [password]"; ""; "[numbers] = SMS numbers (if multiple, enclose in quotes) to send message to."; "[message] = Text of message you want to send"; "[username] = Username assocated with Mollie account"; "[sender] = Sender" "[password] = MD5 HTTP API Password assocated with Mollie account"; "" "-d = Dry run, pretend success." "" "The numbers, sender, username and password options are optional and"; "override the account credentials defined in $config_file."; "" "A log file $log_file will be kept if it is writable to the user." ""; 1; # Get command line arguments [ "$1" != "" ] ; $1 -n) # Get the SMS numbers that we should send message to numbers="$2"; 2; ;; -m) # Get the message we should send message="$2"; 2; ;; -s) # Get the sender to show in the SMS sender="$2"; 2; ;; -u) # Get the username username="$2"; 2; ;; -p) # Get the password password="$2"; 2; ;; -d) dry_run="dry_run"; 1; ;; *) "Unknown option: $1" 1; ;; [ -z "$username" ]; logfail "No username specified or found in $config_file." [ -z "$password" ]; logfail "No password specified or found in $config_file." [ -z "$numbers" ]; logfail "No numbers specified or found in $config_file." message_length= -n "$message"|wc -c` sender_length= -n "$sender"|wc -c` [ "$message_length" -gt "160" ]; logfail "SMS message is longer than 160 chars ($message_length). That is not allowed." >&2 [ "$sender_length" -gt "11" ]; logfail "Sender is longer than 11 chars ($sender_length). That is not allowed." >&2 # We haven't sent the message yet message_sent_ok=0; # The API supports sending to a comma seperated list, but that doesn't seem # to work well. Therefore, I space seperate them and just call the API for # each number. number $numbers; [ ! "$dry_run" ]; RESPONSE=`curl -s -d username="$username" -d md5_password="$password" -d originator="$sender" -d recipients="$number" -d message="$message" http://www.mollie.nl/xml/sms/` RESPONSE="<success>true</success>" # Curl was able to post okay... [ "$?" -eq "0" ]; success_line_true= "$RESPONSE"|grep -i "<success>true</success>"` [ -z "$success_line_true" ]; logfail "$message to $number. Response was: $RESPONSE" logfail "curl return an error while trying to contact mollie to send an SMS." >&2 [ -w "$log_file" ]; "Success: [`date`]: $dry_run Sent '$message' to $number" >> $log_file "Success sending sms(es)."
It has the following config file in /etc/notify_sms.conf:
# Default values. Can be overriden with command line arguments username="halfgaar" password="" # MD5sum of HTTP API password. sender="Melk" numbers="+316xxxxxxxx" # You can set multiple numbers by space seperating them.
I’m always confused by bash’s quoting. I hope to put all my quote wisdom in this post and invoke other’s quote wisdom in the comments. I’ll give some examples of what I mean.
Let’s say you have a file with a space: “bla bla.txt”. If I were to ls that file, I would do:
ls 'bla bla.txt'
This works. However, when I want to do this from a variable (in a script) and do:
command="ls 'bla bla.txt'" $command
The result is:
ls: cannot access 'bla: No such file or directory ls: cannot access bla.txt': No such file or directory
You can solve this by using eval:
command="ls 'bla bla.txt'" eval $command
This gives:
bla bla.txt
Some time ago, I suggested this as answer on somebodies question at userfriendly, to which somebody else said that using eval actually makes things worse:
That’s actually worse. . . as the quoting gets re-parsed (remember, ‘eval’ means “take arguments as shell input”), which means that single quotes in the name break it, horribly, and names with spaces get even _worse_.
Another example: let’s say you have two files:
-rw-r----- 1 halfgaar halfgaar 0 2009-10-18 16:51 bla's bla"s.txt -rw-r----- 1 halfgaar halfgaar 0 2009-10-18 16:52 normal.txt
I’m gonna run this command on it: find . -mindepth 1 -exec ls ‘{}’ \;. When executed without eval, it says this:
find: missing argument to `-exec'
With eval, it says:
./normal.txt ./bla's bla"s.txt
Eval seems to be what I need, so what is wrong with using it? Also, shouldn’t that double quote be a problem? If someone can give a situation where that poses problems, I’m all ears.
© 2024 BigSmoke
Theme by Anders Noren — Up ↑
Recent Comments