Bash trap conditional command - bash

can you help me understand this line I found in a script:
trap "[[ -d ${temp_dir} ]] && rm -rf ${temp_dir}" EXIT
The way I understand is, the trap command waits for EXIT signal and upon that, it removes the directory, if it exists.
Is this correct or can anybody educate me?

Your interpretation is correct, although there are two changes I would make to the code itself:
$temp_dir needs to be quoted in the command to ensure it works correctly for any valid directory name. Let's say the value of temp_dir is /tmp/some dir. As is, the trap command will be
[[ -d /tmp/some dir ]] && rm -rf /tmp/some dir
which is incorrect, as the parameter is expanded before the trap command sets the error handler. Add some quotes to fix it:
trap "[[ -d '${temp_dir}' ]] && rm -rf '${temp_dir}'" EXIT
Depending on when the value of temp_dir is set, you might consider putting the entire command in single quotes, so that temp_dir isn't expanded until the command is actually run:
trap '[[ -d $temp_dir ]] && rm -rf "$temp_dir"' EXIT
However, it's usually simpler to keep the handler as simple as possible. Define a function to call:
clean_up_temp () {
[[ -d $temp_dir ]] && rm -rf "$temp_dir"
}
and just call the function from the handler:
trap clean_up_temp EXIT

Related

Chaining OR and AND in Bash

I would like to chain the OR and AND commands so that I print the contents of a directory to stdout if the directory exists, or in the case that it does not, print a message to stdout saying that the directory "$MY_DIR" is being created and then create it.
I have the following code.
ls "$MY_DIR" || echo "Creating $MY_DIR" && mkdir -p "$MY_DIR"
Is this the correct and canonical way to do this? Will mkdir always run since echo will return 0 return status, even in the case that ls does return?
The most relevant question I have located so far is this one which does not eliminate my doubts.
I would not do what you are doing that way in any case. You should group your commands for clarity if for no other reason.
ls "$MY_DIR" || {
echo "Creating $MY_DIR"
mkdir -p "$MY_DIR"
}
This has several advantages: your intent is more clearly expressed, there is less ambiguity between what the human thinks will happen and what the computer will do, and it stops relying on the exit code from echo that you were not really interested in to begin with. Even if your original version worked entirely correctly it was more vulnerable to later, naïve modification.
A oneliner form is of course possible, if less readable:
ls "$MY_DIR" || { echo "Creating $MY_DIR"; mkdir -p "$MY_DIR"; }
As for your original method, consider this:
If the ls command fails:
false || true && echo mkdir # prints mkdir
But if the ls command succeeds
true || true && echo mkdir # also prints mkdir
Whereas
true || { true; echo mkdir; } # does not print
false || { true; echo mkdir; } # prints mkdir
It gets worse: I am not entirely clear whether ls will set an unsuccessful return code if the file/directory does not exist. It's certainly true that GNU ls does this, and it may be common, but the standard doesn't seem to say what constitutes success or failure, so implementations may well disagree.
Why does it have to be chaining, with all the potential problems that might bring? Why not express your intentions clearly?
Also, UPPERCASE is usually reserved for environment variables.
# -d checks for directory specifically, use -e for existence.
# Thanks to #sorpigal for pointing it out.
if [[ -d $my_dir ]]
then
ls "$my_dir"
else
echo "Creating $my_dir"
mkdir -p "$my_dir"
fi
Or, if it has to be one line...
if [[ -d $my_dir ]]; then ls "$my_dir"; else echo "Creating $my_dir"; mkdir -p "$my_dir"; fi

"cd" inside "if" condition in Bash does not change the current directory after "if"

I am trying to do "cd" inside "if" condition in a Bash script. It stays in the same directory after "if". So I have to do "cd" outside "if" and then use $? value in if. Is there a way to avoid using this extra step? What is the shortest possible way of doing it?
See three variants of my code:
#!/bin/bash
set +e
# If I write the following:
if ! (rm -rf sim && mkdir sim && cd sim); then
echo $0: Cannot prepare simulation directory
exit 1
fi
# it does not change the current directory
echo $PWD
# Also, if I write the following:
if ! rm -rf sim || ! mkdir sim || ! cd sim; then
echo $0: Cannot prepare simulation directory
exit 1
fi
# it also does not change the current directory
echo $PWD
# In order to change the current directory, I need to write:
rm -rf sim && mkdir sim && cd sim
if [ $? -eq 1 ]; then
echo $0: Cannot prepare simulation directory
exit 1
fi
# Now it prints .../sim
echo $PWD
cd ..
# Is it possible to write it without an additional line with $?
exit
Parenthesis in bash create a subshell -- a fork()-ed off copy of the shell with its own environment variables, its own current directory, etc; thus, in your first attempt, the cd took effect only until the closing paren ended the subshell. (POSIX doesn't strictly require this subshell behavior, but it does require that the environment created by parenthesis have its own working directory, so cd's effects will be scoped on all standard-compliant shells, whether or not that shell actually uses a fork() here in all circumstances).
Use curly brackets, not parenthesis, for grouping when you don't want to create a subshell. That is:
if ! { rm -rf sim && mkdir sim && cd sim; }; then
echo "$0: Cannot prepare simulation directory"
exit 1
fi
That said, your second approach works perfectly well (though it's unwieldy to write and not a conventional idiom).
bash <<'EOF'
cd /tmp
echo "Starting in $PWD"
if ! rm -rf sim || ! mkdir sim || ! cd sim; then
echo "$0: Cannot prepare simulation directory"
exit 1
fi
echo "Ending in $PWD"
EOF
...properly emits:
Starting in /tmp
Ending in /tmp/sim

Delete a directory only if it exists using a shell script

I have a shell (ksh) script. I want to determine whether a certain directory is present in /tmp, and if it is present then I have to delete it. My script is:
test
#!/usr/bin/ksh
# what should I write here?
if [[ -f /tmp/dir.lock ]]; then
echo "Removing Lock"
rm -rf /tmp/dir.lock
fi
How can I proceed? I'm not getting the wanted result: the directory is not removed when I execute the script and I'm not getting Removing Lock output on my screen.
I checked manually and the lock file is present in the location.
The lock file is created with set MUTEX_LOCK "/tmp/dir.lock" by a TCL program.
In addition to -f versus -d note that [[ ]] is not POSIX, while [ ] is. Any string or path you use more than once should be in a variable to avoid typing errors, especially when you use rm -rf which deletes with extreme prejudice. The most portable solution would be
DIR=/tmp/dir.lock
if [ -d "$DIR" ]; then
printf '%s\n' "Removing Lock ($DIR)"
rm -rf "$DIR"
fi
For directory check, you should use -d:
if [[ -d /tmp/dir.lock ]]; then
echo "Removing Lock"
rm -rf /tmp/dir.lock
fi

self-deleting shell script

I've looked around for an answer to this one but couldn't find one.
I have written a simple script that does initial server settings and I'd like it to remove/unlink itself from the root directory on completion. I've tried a number of solutions i googled ( for example /bin/rm $test.sh) but the script always seems to remain in place. Is this possible? Below is my script so far.
#! /bin/bash
cd /root/
wget -r -nH -np --cut-dirs=1 http://myhost.com/install/scripts/
rm -f index.html* *.gif */index.html* */*.gif robots.txt
ls -al /root/
if [ -d /usr/local/psa ]
then
echo plesk > /root/bin/INST_SERVER_TYPE.txt
chmod 775 /root/bin/*
/root/bin/setting_server_ve.sh
rm -rf /root/etc | rm -rf /root/bin | rm -rf /root/log | rm -rf /root/old
sed -i "75s/false/true/" /etc/permissions/jail.conf
exit 1;
elif [ -d /var/webmin ]
then
echo webmin > /root/bin/INST_SERVER_TYPE.txt
chmod 775 /root/bin/*
/root/bin/setting_server_ve.sh
rm -rf /root/etc | rm -rf /root/bin | rm -rf /root/log | rm -rf /root/old
sed -i "67s/false/true/" /etc/permissions/jail.conf
break
exit 1;
else
echo no-gui > /root/bin/INST_SERVER_TYPE.txt
chmod 775 /root/bin/*
/root/bin/setting_server_ve.sh
rm -rf /root/etc | rm -rf /root/bin | rm -rf /root/log | rm -rf /root/old
sed -i "67s/false/true/" /etc/permissions/jail.conf
break
exit 1;
fi
rm -- "$0"
Ought to do the trick. $0 is a magic variable for the full path of the executed script.
This works for me:
#!/bin/sh
rm test.sh
Maybe you didn't really mean to have the '$' in '$test.sh'?
The script can delete itself via the shred command (as a secure deletion) when it exits.
#!/bin/bash
currentscript="$0"
# Function that is called when the script exits:
function finish {
echo "Securely shredding ${currentscript}"; shred -u ${currentscript};
}
# Do your bashing here...
# When your script is finished, exit with a call to the function, "finish":
trap finish EXIT
The simplest one:
#!/path/to/rm
Usage: ./path/to/the/script/above
Note: /path/to/rm must not have blank characters at all.
I wrote a small script that adds a grace period to a self deleting script based on
user742030's answer https://stackoverflow.com/a/34303677/10772577.
function selfShred {
SHREDDING_GRACE_SECONDS=${SHREDDING_GRACE_SECONDS:-5}
if (( $SHREDDING_GRACE_SECONDS > 0 )); then
echo -e "Shreding ${0} in $SHREDDING_GRACE_SECONDS seconds \e[1;31mCTRL-C TO KEEP FILE\e[0m"
BOMB="●"
FUZE='~'
SPARK="\e[1;31m*\e[0m"
SLEEP_LEFT=$SHREDDING_GRACE_SECONDS
while (( $SLEEP_LEFT > 0 )); do
LINE="$BOMB"
for (( j=0; j < $SLEEP_LEFT - 1; j++ )); do
LINE+="$FUZE"
done
LINE+="$SPARK"
echo -en $LINE "\r"
sleep 1
(( SLEEP_LEFT-- ))
done
fi
shred -u "${0}"
}
trap selfShred EXIT
See the repo here: https://github.com/reedHam/self-shred
$0 may not contain the script's name/path in certain circumstances. Please check the following: https://stackoverflow.com/a/35006505/5113030 (Choosing between $0 and BASH_SOURCE...)
The following script should work as expected in these cases:
source script.sh - the script is sourced;
./script.sh - executed interactively;
/bin/bash -- script.sh - passed as an argument to a shell program.
#!/usr/bin/env bash
# ...
rm -- "$( readlink -f -- "${BASH_SOURCE[0]:-$0}" 2> '/dev/null'; )";
Please check the following regarding shell script source reading and execution since it may affect the behavior when a script is deleted while running: https://unix.stackexchange.com/a/121025/133353 (How Does Linux deal with shell scripts?...)
Related: https://stackoverflow.com/a/246128/5113030 (How can I get the source directory of a Bash script from...)
Just add to the end:
rm -- "$0"
Why remove the script at all? As other have mentioned it means you have to keep a copy elsewhere.
A suggestion is to use a "firstboot" like approach. Simply create an empty file in e.g. /etc/sysconfig that triggers the execution of this script if it is present. Then remove that file at the end of the script.
Modify the script so it has the necessary chkconfig headers and place it in /etc/init.d/ so it is run at every boot.
That way you can rerun the script at a later time simply by recreating the trigger script.
Hope this helps.

Temporary operation in a temporary directory in shell script

I need a fresh temporary directory to do some work in a shell script. When the work is done (or if I kill the job midway), I want the script to change back to the old working directory and wipe out the temporary one. In Ruby, it might look like this:
require 'tmpdir'
Dir.mktmpdir 'my_build' do |temp_dir|
puts "Temporary workspace is #{temp_dir}"
do_some_stuff(temp_dir)
end
puts "Temporary directory already deleted"
What would be the best bang for the buck to do that in a Bash script?
Here is my current implementation. Any thoughts or suggestions?
here=$( pwd )
tdir=$( mktemp -d )
trap 'return_here' INT TERM EXIT
return_here () {
cd "$here"
[ -d "$tdir" ] && rm -rf "$tdir"
}
do_stuff # This may succeed, fail, change dir, or I may ^C it.
return_here
Here you go:
#!/bin/bash
TDIR=`mktemp -d`
trap "{ cd - ; rm -rf $TDIR; exit 255; }" SIGINT
cd $TDIR
# do important stuff here
cd -
rm -rf $TDIR
exit 0
The usual utility to get yourself a temporary directory is mktemp -d (and the -p option lets you specify a prefix directory).
I'm not sure if "I want to trap" was a question too, but bash does let you trap signals with (surprise!) trap - see the documentation.
Assuming mktemp -d returns a relative pathname, I would forget about $here and do this instead:
tdir=
cleanup() {
test -n "$tdir" && test -d "$tdir" && rm -rf "$tdir"
}
tdir="$(pwd)/$(mktemp -d)"
trap cleanup EXIT
trap 'cleanup; exit 127' INT TERM
# no need to call cleanup explicitly, unless the shell itself crashes, the EXIT trap will run it for you

Resources