Refreshing variable just before invoking it? - bash

I have some easy backup script to tar, xz and save to SMB share some folders and want to use variable ($invocation) for a part of the command.
Unfortunately, when script runs it remembers that the variable $location is empty and a result of it is no backup
(xz: /mnt/backupMatrix//2020-06-05T00-27.tar.xz: No such file or directory).
I do know why it's not working, but how to do it correctly?
How to "reload" variable $invocation (with freshly set $location) just before it's launch, WITHOUT hardcoding whole invocation to the function?
Code below.
#! /bin/bash
invocation="xz --threads=8 -z -9 -v -v - > /mnt/backupMatrix/$location/$(date +%Y-%m-%dT%H-%M).tar.xz"
function mcuser2
{
#backup mcuser2
location="mcuser2"
echo "setting var location to: $location"
echo "Stopping minecraft2.service"
sudo systemctl stop minecraft2.service
echo "Stopped minecraft2.service"
echo "Starting taring mcuser2"
# --exclude='/home/mcuser2/world'
tar cpf - --exclude='/home/mcuser2/plugins/dynmap/web/tiles' /home/mcuser2/ | $invocation
echo "Completed taring mcuser2"
echo "starting minecraft2.service"
sudo systemctl start minecraft2.service
echo "started successfully minecraft2.service"
}
case "$1" in
mcuser2)
mcuser2
;;
esac

Since you set location unconditionally in the function, the simplest approach is to move that line from the function and place it before you set invocation.
An alternative is to use more variables:
: "${XZ:=xz}"
: "${XZOPTIONS:=--threads=8 -z -9 -v -v -}"
: "${TSTAMP:=$(date +%Y-%m-%dT%H-%M)}"
: "${BASEDIR:=/mnt/backupMatrix}"
: "${TAR:=tar}"
: "${TAROPTIONS:=-cpf -}"
STARTDIR="/home/mcuser2" # $HOME?
EXCLUDE="--exclude=$STARTDIR/plugins/dynmap/web/tiles"
function mcuser2
{
#backup mcuser2
location="mcuser2"
echo "setting var location to: $location"
echo "Stopping minecraft2.service"
sudo systemctl stop minecraft2.service
echo "Stopped minecraft2.service"
echo "Starting tarring mcuser2"
$TAR $TAROPTIONS - "$EXCLUDE" "$STARTDIR" |
$XZ $XZOPTIONS > "$BASEDIR/$location/$TSTAMP.tar.xz"
echo "Completed tarring $STARTDIR"
echo "starting minecraft2.service"
sudo systemctl start minecraft2.service &&
echo "started successfully minecraft2.service"
}
Warning: untested shell scripting.
I'd probably not include the full path in the archive (GNU tar drops the leading slash anyway); I'd cd to the start directory and specify . as the directory to be backed up — possibly use -C "$STARTDIR" on the tar command line. That would require me to alter the exclude option too, I expect, using a relative path instead of an absolute path.
You could also think of using shell arrays for the options lists.
I'm not convinced I'd use a function in this script; it doesn't really seem to help very much.

Related

How to run shell script from bash script

I get an error (on line: sh up.sh) running the following:
#!/bin/bash
# Install angular components
echo "Installing Angular Components..."
cd angApp
npm install
# Install Server components
echo "Installing Backend Components..."
cd ..
cd APIServer
# go back to main dir
cd ..
# ask to see if we should launch server
echo "Do you want to launch the server now? Enter (yes/no) "
read shouldLaunch
# Launch if requested. Otherwise end build
if [ "$shouldLaunch" == "yes" ]; then
echo "Great! Launching the servers for you..."
sh up.sh
else
echo "No problem..."
echo "you can launch the server by doing ./up.sh"
echo "bye!"
fi
How do I run the up.sh script?
If the up.sh file is in the same directory as the file containing the code above then you can do
echo "Great! Launching the servers for you..."
$(dirname $0)/up.sh
The variable $0 is the path of the current script, dirname strips off the last segment of the path, and $(...) turns the output of dirname into a string.
To avoid cd-ing mess, simply run the parts in subshells, like:
#!/bin/bash
(
# Install angular components - in shubshell
echo "Installing Angular Components..."
cd angApp
npm install
)
(
# Install Server components - again in subshell
echo "Installing Backend Components..."
cd APIServer
#do something here
)
# go back to main dir
#cd .. #not needed, you're now in the parent shell...
# ask to see if we should launch server
echo "Do you want to launch the server now? Enter (yes/no) "
read shouldLaunch
# Launch if requested. Otherwise end build
if [ "$shouldLaunch" == "yes" ]; then
echo "Great! Launching the servers for you..."
sh up.sh
else
echo "No problem..."
echo "you can launch the server by doing ./up.sh"
echo "bye!"
fi

Run wget and other commands in shell script

I'm trying to create a shell script that I will download the latest Atomic gotroot rules to my server, unpack them, copy them to the correct folder, etc.,
I've been reading shell tutorials and forum posts for most of the day and the syntax escapes me for some of these. I have run all these commands and I know they work if I manually run them.
I know I need to develop some error checking, but I'm just trying to get the commands to run correctly. The main problem at the moment is the syntax of the wget commands, i've got errors about missing semi-colons, divide by zero, unsupported schemes - I've tried various quoting (single and double) and escaping - / " characters in various combinations.
Thanks for any help.
The raw wget command is
wget --user="jim" --password="xxx-yyy-zzz" "http://updates.atomicorp.com/channels/rules/subscription/VERSION"
#!/bin/sh
update_modsec_rules(){
wget=/usr/bin/wget
tar=/bin/tar
apachectl=/usr/bin/apache2ctl
TXT="Script Run Finished"
WORKING_DIR="/var/asl/updates"
TARGET_DIR="/usr/local/apache/conf/modsec_rules/"
EXISTING_FILES="/var/asl/updates/modsec/*"
EXISTING_ARCH="/var/asl/updates/modsec-*"
WGET_OPTS='--user=jim --password=xxx-yyy-zzz'
URL_BASE="http://updates.atomicorp.com/channels/rules/subscription"
# change to working directory and cleanup any downloaded files and extracted rules in modsec/ directory
cd $WORKING_DIR
rm -f $EXISTING_ARCH
rm -f $EXISTING_FILES
rm -f VERSION*
# wget to download VERSION file
$wget ${WGET_OPTS} "${URL_BASE}/VERSION"
# get current MODSEC_VERSION from VERSION file and save as variable
source VERSION
TARGET_DATE=$MODSEC_VERSION
echo $TARGET_DATE
# wget to download current archive
$wget ${WGET_OPTS} "${URL_BASE}/modsec-${TARGET_DATE}.tar.gz"
# extract archive
echo "extracting files . . . "
tar zxvf $WORKING_DIR/modsec-${TARGET_DATE}.tar.gz
echo "copying files . . . "
cp -uv $EXISTING_FILES $TARGET_DIR
echo $TXT
}
update_modsec_rules $# 2>&1 | tee -a /var/asl/modsec_update.log
RESTART_APACHE="/usr/local/cpanel/scripts/restartsrv httpd"
$RESTART_APACHE
Here are some guidelines to use when writing shell scripts.
Always quote variables when you use them. This helps avoid the possibility of misinterpretation. (What if a filename contains a space?)
Don't trust fileglobbing on commands like rm. Use for loops instead. (What if a filename starts with a hyphen?)
Avoid subshells when possible. Your lines with backquotes make me itchy.
Don't exec if you can help it. And especially don't expect any parts of your script after your exec to actually get run.
I should point out that while your shell may be bash, you've specified /bin/sh for execution of this script, so it is NOT a bash script.
Here's a rewrite with some error checking. Add salt to taste.
#!/bin/sh
# Linux
wget=/usr/bin/wget
tar=/bin/tar
apachectl=/usr/sbin/apache2ctl
# FreeBSD
#wget=/usr/local/bin/wget
#tar=/usr/bin/tar
#apachectl=/usr/local/sbin/apachectl
TXT="GOT TO THE END, YEAH"
WORKING_DIR="/var/asl/updates"
TARGET_DIR="/usr/local/apache/conf/modsec_rules/"
EXISTING_FILES_DIR="/var/asl/updates/modsec/"
EXISTING_ARCH="/var/asl/updates/"
URL_BASE="http://updates.atomicorp.com/channels/rules/subscription"
WGET_OPTS='--user="jim" --password="xxx-yyy-zzz"'
if [ ! -x "$wget" ]; then
echo "ERROR: No wget." >&2
exit 1
elif [ ! -x "$apachectl" ]; then
echo "ERROR: No apachectl." >&2
exit 1
elif [ ! -x "$tar" ]; then
echo "ERROR: Not in Kansas anymore, Toto." >&2
exit 1
fi
# change to working directory and cleanup any downloaded files
# and extracted rules in modsec/ directory
if ! cd "$WORKING_DIR"; then
echo "ERROR: can't access working directory ($WORKING_DIR)" >&2
exit 1
fi
# Delete each file in a loop.
for file in "$EXISTING_FILES_DIR"/* "$EXISTING_ARCH_DIR"/modsec-*; do
rm -f "$file"
done
# Move old VERSION out of the way.
mv VERSION VERSION-$$
# wget1 to download VERSION file (replaces WGET1)
if ! $wget $WGET_OPTS $URL_BASE}/VERSION; then
echo "ERROR: can't get VERSION" >&2
mv VERSION-$$ VERSION
exit 1
fi
# get current MODSEC_VERSION from VERSION file and save as variable,
# but DON'T blindly trust and run scripts from an external source.
if grep -q '^MODSEC_VERSION=' VERSION; then
TARGET_DATE="`sed -ne '/^MODSEC_VERSION=/{s/^[^=]*=//p;q;}' VERSION`"
echo "Target date: $TARGET_DATE"
fi
# Download current archive (replaces WGET2)
if ! $wget ${WGET_OPTS} "${URL_BASE}/modsec-$TARGET_DATE.tar.gz"; then
echo "ERROR: can't get archive" >&2
mv VERSION-$$ VERSION # Do this, don't do this, I don't know your needs.
exit 1
fi
# extract archive
if [ ! -f "$WORKING_DIR/modsec-${TARGET_DATE}.tar.gz" ]; then
echo "ERROR: I'm confused, where's my archive?" >&2
mv VERSION-$$ VERSION # Do this, don't do this, I don't know your needs.
exit 1
fi
tar zxvf "$WORKING_DIR/modsec-${TARGET_DATE}.tar.gz"
for file in "$EXISTING_FILES_DIR"/*; do
cp "$file" "$TARGET_DIR/"
done
# So far so good, so let's restart apache.
if $apachectl configtest; then
if $apachectl restart; then
# Success!
rm -f VERSION-$$
echo "$TXT"
else
echo "ERROR: PANIC! Apache didn't restart. Notify the authorities!" >&2
exit 3
fi
else
echo "ERROR: Apache configs are broken. We're still running, but you'd better fix this ASAP." >&2
exit 2
fi
Note that while I've rewritten this to be more sensible, there is certainly still a lot of room for improvement.
You have two options:
1- changing this to
WGET1=' --user="jim" --password="xxx-yyy-zzz" "http://updates.atomicorp.com/channels/rules/subscription/VERSION"'
then run
wget $WGET1 same to WGET2
Or
2- encapsulating $WGET1 with backquotes ``.
e.g.:
`$WGET`
This applies to any command your executing out of a variable.
Suggested changes:
#!/bin/sh
TXT="GOT TO THE END, YEAH"
WORKING_DIR="/var/asl/updates"
TARGET_DIR="/usr/local/apache/conf/modsec_rules/"
EXISTING_FILES="/var/asl/updates/modsec/*"
EXISTING_ARCH="/var/asl/updates/modsec-*"
WGET1='wget --user="jim" --password="xxx-yyy-zzz" "http://updates.atomicorp.com/channels/rules/subscription/VERSION"'
WGET2='wget --user="jim" --password="xxx-yyy-zzz" "http://updates.atomicorp.com/channels/rules/subscription/modsec-$TARGET_DATE.tar.gz"'
## change to working directory and cleanup any downloaded files and extracted rules in modsec/ directory
cd $WORKING_DIR
rm -f $EXISTING_ARCH
rm -f $EXISTING_FILES
## wget1 to download VERSION file
`$WGET1`
## get current MODSEC_VERSION from VERSION file and save as variable
source VERSION
TARGET_DATE=`echo $MODSEC_VERSION`
## WGET2 command to download current archive
`$WGET2`
## extract archive
tar zxvf $WORKING_DIR/modsec-$TARGET_DATE.tar.gz
cp $EXISTING_FILES $TARGET_DIR
## restart server
exec '/usr/local/cpanel/scripts/restartsrv_httpd' $*;
Pro Tip: If you need string substitution, using ${VAR} is much better to eliminate ambiguity, e.g.:
tar zxvf $WORKING_DIR/modsec-${TARGET_DATE}.tar.gz

Should this be done with bash script or automator or applescript?

I have bash command that contains a variable to a file which updates the firmware for a specific hardware and give it a serial number.
#!/bin/bash
fpath=$(dirname "$0")
ee_image=mlr-2000119.bin
sudo nvram tbt-options=4
sudo /usr/sbin/bless -mount / -firmware "$fpath/ThorUtilDevice.efi" -payload "$fpath/$ee_image" -options "-o -ej 1 -blast efi-apple-payload0-data"
sudo reboot now
I would like to create a file through automator or applescript that will create this same file but will automatically increase the ee_image bin file name by one. So that the end user doesn't always have to open the command file in text edit, make the change manually then save then execute the file..
Any help with this would be a God send.
The last line in your script sudo reboot now would make any sort of loop meaningless.
However, if you insist, use may a loop:
#!/bin/bash
fpath=$(dirname "$0")
for i in {2000119..3000119}; do
ee_image=mlr-${i}.bin
sudo nvram tbt-options=4
sudo /usr/sbin/bless -mount / -firmware "$fpath/ThorUtilDevice.efi" -payload "$fpath/$ee_image" -options "-o -ej 1 -blast efi-apple-payload0-data"
sudo reboot now
done
This would loop through mlr-2000119.bin to mlr-3000119. You can also consider passing an argument to the script in which case you can use your original script with the ee_image line as
ee_image=mlr-$1.bin
and invoke bash /path/to/your/script.sh 2000119
#devnull wrote:
The last line in your script sudo reboot now would make any sort of loop meaningless.
I believe that the reboot command is just like any other command. It should be echoed to a file rather than being run to generate the script for the end-user.
I think that a good idea would be to have a script that creates scripts.
This is similar to how many websites work. The script on the server can echo HTML, CSS, and JavaScript code for consumption by the web browser.
Here is an example:
#!/bin/bash
# set the path to the dir
dir=$(dirname $0)"/"
# set the path to the file that keeps track of the serial numbers
snFile="$dir""sn.txt"
# set the file name of the file to be generated
fileName="serialize"
# read the last serial number used
if [ -e "$snFile" ];
then
read lastSN < "$snFile"
else
lastSN="0"
fi
# increment the serial number
let "nextSN = $lastSN + 1"
echo "$nextSN" > "$snFile"
# create a path variable for the file being created.
generatedPath="$dir$fileName$nextSN.sh"
# generate the script
echo "#!/bin/bash" > "$generatedPath"
echo 'fpath=$(dirname "$0")' >> "$generatedPath"
echo '' >> "$generatedPath"
echo "ee_image=mlr-$nextSN.bin" >> "$generatedPath"
echo '' >> "$generatedPath"
echo 'sudo nvram tbt-options=4' >> "$generatedPath"
echo '' >> "$generatedPath"
echo 'sudo /usr/sbin/bless -mount / -firmware \"$fpath/ThorUtilDevice.efi\" -payload \"$fpath/$ee_image\" -options \"-o -ej 1 -blast efi-apple-payload0-data\" \' >> "$generatedPath"
echo '' >> "$generatedPath"
echo 'sudo reboot now' >> "$generatedPath"
# give the user some feedback
echo "generatedPath: $generatedPath"
If having your end-user run a bash script is good enough, then I think that you're almost done.
If you want to have an even better user interface and have a Mac application for the end-user to run, send me an email and I can help you with that.
kaydell#learnbymac.com

LOCAL_DIR variable prepends the scripts current directory (totally not what I expect)

Consider the following simple rsync script I am tryint to slap up:
#!/bin/bash
PROJECT="$1"
USER=stef
LOCAL_DIR="~/drupal-files/"
REMOTE_HOST="hostname.com"
REMOTE_PROJECTS_PATH=""
# Should not have anything to change below
PROJECT_LIST="proj1 proj2 proj3 quit"
echo "/nSelect project you wish to rsync\n\n"
select PROJECT in $PROJECT_LIST
do
if [ "$PROJECT" = "quit" ]; then
echo
echo "Quitting $0"
echo
exit
fi
echo "Rsynching $PROJECT from $REMOTE_HOST into" $LOCAL_DIR$PROJECT
rsync -avzrvP $USER#$REMOTE_HOST:/var/projects/$PROJECT/ $LOCAL_DIR$PROJECT
done
echo "Rsync complete."
exit;
The variable $LOCALDIR$PROJECT set in the rsync command always includes the scripts path, :
OUTPUT:
Rsynching casa from hostname.com.com into ~/drupal-files/casa
opening connection using: ssh -l stef hostname.com rsync --server --sender -vvlogDtprz e.iLsf . /var/groupe_tva/casa/
receiving incremental file list
rsync: mkdir "/home/stef/bin/~/drupal-files/proj1" failed: No such file or directory (2)
rsync error: error in file IO (code 11) at main.c(605) [Receiver=3.0.9]
The line with mkdir should not have /home/stef/bin, why is bash adding the script's running dir on the variable?
Thanks
LOCAL_DIR="~/drupal-files/"
The string is in quotes so there's pathname expansion, and the variable will contain the literal string.
Remove the quotes.
$ x="~/test"; echo $x
~/test
$ x=~/test; echo $x
/home/user/test

exiting script while running source scriptname over SSH

I have a script with a number of options in it one of the option sets is supposed to change the directory and then exit the script however running over ssh with the source to get it to change in the parent it exits SSH is there another way to do this so that it does not exit? my script is in the /usr/sbin directory.
You might try having the script run a subshell instead of whatever method it is using to “change [the directory] in the parent” (presumably you have the child print out a cd command and have the parent do something like eval "$(script --print-cd)"). So instead of (e.g.) a --print-cd option, add a --subshell option that starts a new instance of $SHELL.
d=/path/to/some/dir
#...
cd "$d"
#...
if test -n "$opt_print_cd"; then
sq_d="$(printf %s "$d" | sed -e "s/'/'\\\\''/g")"
printf "cd '%s'\n" "$sq_d"
elif test -n "$opt_subshell"; then
exec "$SHELL"
fi
If you can not edit the script itself, you can make a wrapper (assuming you have permission to create new, persistent files on the ‘server’):
#!/bin/sh
script='/path/to/script'
print_cd=
for a; do test "$a" = --print-cd && print_cd=yes && break; done
if test -n "$print_cd"; then
eval "$("$script" ${1+"$#"})" # use cd instead of eval if the script prints a bare dir path
exec "$SHELL"
else
exec $script" ${1+"$#"}
fi

Resources