Assign exit status and arguments with shell script (unix) - shell

For all the files (in the current directory) that contain the WORD that is the 1st command line argument, I have to insert the second command line argument on a new line at the head of the file.Then I have to Print usage and exit with status 4 if not given two args. Exit status 1 is there were there are no such files. Exit status 2 if any of the files could not be altered. Exit status 0 otherwise.
For Example:
Suppose the script filename is addwarn.sh then,
echo "I am a string" > f1
echo "I am not a string > f2
./addwarn.sh "not" '*** WARNING ***'
cat f1 f2
I am a string
*** WARNING ***
I am not a string
What I have tried so far:
#! bin/sh
if [ $? -eq 0 ]; then
exit
fi
if [ $? -ne 0 ]; then
cat *
fi
I am not sure how to make a script, any ideas?

#!/bin/sh
found=0
usage(){
echo "$0 takes two args!"
exit 4
}
#check for two args
i=0
for a in "$#"; do
((i++))
done
[ "$i" -ne "2" ] && usage
uneditable=0
for f in $(find ./ -maxdepth 1 -type f); do
if grep -q $1 $f ; then
found=1
echo -e "$2\n$(cat $f)" > $f || uneditable=1
fi
done
[ "$found" = "0" ] && exit 1
[ "$uneditable" = "1" ] && exit 2
exit 0

Related

Last command exit code comparison in .bashrc

As all of you probably know, bash can print the last command exit code. This works for me, but I wanted to improve it by adding an if statement which checks if $? was 0. If it is 0 print the code in white, and if it is different, print it in red. Unfortunately this does not seem to work:
if [ $? == "0" ]; then
PS1=${PS1}'$(echo ${?})'
else
PS1=${PS1}'\e[1;31m\]$(echo ${?})'
fi
I also tried:
if [ $(echo $?) == "0" ]; then
PS1=${PS1}'$(echo ${?})'
else
PS1=${PS1}'\e[1;31m\]$(echo ${?})'
fi
also:
if [ $(echo ${?}) == "0" ]; then
PS1=${PS1}'$(echo ${?})'
else
PS1=${PS1}'\e[1;31m\]$(echo ${?})'
fi
None of them working.
Somehow variable is always 0, therefore printed in white.
How is it possible that I can print exit code, but cannot examine it with "if" ? Is this a bash limitation, or I am doing something wrong?
Just add $? to the PS1.
> PS1='PS $ '
> PS $ echo 1
> 1
> PS $ PS1='PS $? $ '
> PS 0 $ false
> PS 1 $ true
> PS 0 $
If you want to change color, you would have to do it inside PS1, not statically... Also note that $? will change it's value, so you need to save it.
PS1=${PS1}'$(ret=$?; if [ "$ret" -ne 0 ]; then printf "\e[1;31m\]"; fi; printf "%d" "$ret")'
$? is the exit status of the last command. If you execute some command, for instance [ … = … ], then $? changes. Example:
myCmd
echo $? # prints exit status of myCmd
echo $? # prints exit status of echo
myCmd
if [ $? = 0 ]; then
echo $? # prints exit status of `[`, here 0
else
echo $? # prints exit status of `[`, here 1
fi
Store the exit status of myCmd in a separate variable if you want to access it later.
myCmd
exitStatus=$?
echo $exitStatus # prints exit status of myCmd
echo $exitStatus # prints exit status of myCmd
By the way: "$(echo ${?})" is overly complicated; just "$?" is better in every way.

Having difficulty writing a script to sort words

I am dealing with sorting words in Bash according to a given argument. I am given either argument -r, -a , -v or -h and according to it there are options to sort the words, as you can see at my "help".
Somehow, if I pass the argument -r it creates an error. I really don't understand what I am doing wrong, as if[["$arg"=="-a"]] works, but I have to use case somehow.
Here is my code:
#!/bin/bash
# Natalie Zubkova , zubkonat
# zubkonat#cvut.fel.cz , LS
#help
help="This script will calculate occurances of words in a given file, and it will sort them according to the given argument in following order> \n
without parametre = increasing order according to a number of occurance\n
-r = decreasing order according to a number of occurance\n
-a = in alphabetical increasing order\n
-a -r = in alphabetical decreasing order\n
There are also special cases of the given parametre, when the script is not sorting but:\n
-h = for obtaining help \n
-v = for obtaining a number of this task "
# this function will divide a given chain into a words, so we can start calculating the occurances, we also convert all the capital letters to the small ones by - tr
a=0;
r=0;
EXT=0;
if [ "$1" == "-h" ]; then
echo $help
exit 0
fi
if [ "$2" == "-h" ]; then
echo $help
exit 0
fi
if [ "$1" == "-v" ]; then
echo "5"
exit 0
fi
if [ "$2" == "-v" ]; then
echo "5"
exit 0
fi
function swap {
while read x y; do
echo "$y" "$x";
done
}
function clearAll {
sed -e 's/[^a-z]/\n/gI' | tr '[A-Z]' '[a-z]' | sort | uniq -c |awk '{i++; if(i!=1) print $2" "$1}' #swap
}
for arg do
case "$arg" in
"-a")
a=1
;;
"-r")
r=1
;;
"-v")
echo "5" #number of task is 5
exit 0
;;
"-h")
echo $help
exit 0
;;
"-?")
echo "invalid parametre, please display a help using argument h"
exit 0
;;
esac
done
#Sort according to parametres -a and -r
function sortWords {
if [[ a -eq 1 ]]; then
if [[ r -eq 0 ]]; then
clearAll | sort -nk1
fi
fi
if [[ a -eq 1 ]]; then
if [[ r -eq 1 ]]; then
clearAll | sort -nk1 -r
fi
fi
if [[ r -eq 1 ]]; then
if [[ a -eq 0 ]]; then
clearAll | sort -nk2 -r
fi
fi
if [[ a -eq 0 ]]; then
if [[ r -eq 0 ]]; then
clearAll | sort -nk2
fi
fi
}
#code is from Stackoverflow.com
function cat-all {
while IFS= read -r file
do
if [[ ! -z "$file" ]]; then
cat "$file"
fi
done
}
#histogram
hist=""
for arg do
if [[ ! -e "$arg" ]]; then
EXT=1;
echo "A FILE DOESNT EXIST" >&2
continue;
elif [[ ! -f "$arg" ]]; then
EXT=1;
echo "A FILE DOESNT EXIST" >&2
continue;
elif [[ ! -r "$arg" ]]; then
EXT=1;
echo "A FILE DOESNT EXIST" >&2
continue;
fi
done
for arg do
hist="$hist""$arg""\n"
done
echo -e "$hist" | cat-all | sortWords
exit $EXT;
Here is what our upload system which does some test to see if our program works says:
Test #6
> b5.sh -r ./easy.txt
ERROR: script output is wrong:
--- expected output
+++ script stdout
## --- line 1 (167 lines) ; +++ no lines ##
-the 89
-steam 46
-a 39
-of 37
-to 35
...
script written 484 lines, while 484 lines are expected
script error output:
A FILE DOESNT EXIST
cat: invalid option -- 'r'
Try `cat --help' for more information.
script exit value: 1
ERROR: Interrupted due to failed test
If anyone could help me I would really appreciate it.
You forgot to move the parameter index position with shift:
"-r")
r=1
shift
;;
shift above moves to the next command line arg: ./easy.txt in your case.
Without it, read -r file will read -r instead of the file name.

why does not it enter into arg1 if condion, though arg1 is commandline argument

if [ "$#" -ne 1 ]; then
flag=1;
elif ! [[ "$1" == "arg1" || "$1" == "arg2" || "$1" == "arg3" || ...... ]]; then
echo "Invalid"
flag=1;
fi
if [ "$flag" == "1" ]; then
echo "Usage of script...."
exit
fi
count="$(ls *.mov | wc -l)"
if [[ "$count" -eq 0 ]]
then
echo there are 0 .mov files in this path
elif [[ "$count" -eq 1 ]]
then
echo there is 1 .mov file in this path
vlc *.mov
elif [[ $1 = "arg1" ]] ; then
echo entered the tough part....coz its not entering`enter code here`
elif [[ "$1" == "arg2" ]] || [[ "$1" == "arg10" ]] ; then
echo entered here atleast...but not entering
else
script continues
The code does not enter elif conditions involving command line arguments. Tried =, ==, -eq, double square braces, single square braces. But it does not enter, pls help
you should add a disclaimer at the head of the script: " #!/bin/bash"
I tried your script and it does get into the elif.
the code I used is:
if [ "$#" -ne 1 ]; then
echo "not 1 arg"
flag=1;
elif ! [[ "$1" == "arg1" || "$1" == "arg2" || "$1" == "arg3" ]]; then
echo "Invalid"
flag=1;
else
echo "else"
fi
and the input/output are:
$ . script.sh 1 2
not 1 arg
$ . script.sh 1
Invalid
$ . script.sh arg1
else
I tried the second part and it is also working:
count=$2
if [[ "$count" -eq 0 ]] ;then
echo "there are 0 .mov files in this path"
elif [[ "$count" -eq 1 ]] ;then
echo "there is 1 .mov file in this path"
vlc *.mov
elif [[ $1 = "arg1" ]] ; then
echo "arg1 "
elif [[ "$1" == "arg2" ]] || [[ "$1" == "arg10" ]] ; then
echo "arg2 or arg10 "
else
echo "else"
fi
and tested it (second argument is "count"):
$ . script.sh arg1 0
there are 0 .mov files in this path
$ . script.sh 1 0
there are 0 .mov files in this path
$ . script.sh arg10 0
there are 0 .mov files in this path
$ . script.sh arg1 1
there is 1 .mov file in this path
The program 'vlc' is currently not installed. To run 'vlc' please ask your administrator to install the package 'vlc-nox'
$ . script.sh arg10 1
there is 1 .mov file in this path
The program 'vlc' is currently not installed. To run 'vlc' please ask your administrator to install the package 'vlc-nox'
$ . script.sh 1 1
there is 1 .mov file in this path
The program 'vlc' is currently not installed. To run 'vlc' please ask your administrator to install the package 'vlc-nox'
$ . script.sh arg1 2
arg1
$ . script.sh 1 2
else
$ . script.sh arg10 2
arg2 or arg10
You should look into the bash case statement http://mywiki.wooledge.org/BashGuide/TestsAndConditionals#Choices_.28case_and_select.29
For this particular script, it will make your script easier to read and your branching problem will most likely go away..
if your purpose is to handle the arguments passed to the script by command line it's better to use the getopts bash
this is just an example that you can adapt to your scope:
#!/bin/bash
function usage {
echo "usage: ..."
}
while getopts f:o:h opt; do
case $opt in
f)
fileName=$OPTARG
echo "filename[$fileName]"
;;
o)
otherargs=$OPTARG
echo "otherargs[$otherargs]"
;;
h)
usage && exit 0
;;
?)
usage && exit 2
;;
esac
done
~
output
[myShell] ➤ ./n -h
usage: ...
[myShell] ➤ ./n -f myfilename
filename[myfilename]
[myShell] ➤ ./n -o other
otherargs[other]
[myShell] ➤ ./n -l
./n: illegal option -- l
usage: ...

Create parallel processes and wait for all of them to finish, then redo steps

What i want to do should be pretty simple, on my own i have reached the solution below, all i need is a few pointers to tell me if this is the way to do it or i should refactor anything in the code.
The below code, should create a few parallel processes and wait for them to finish executing then rerun the code again and again and again...
The script is triggered by a cron job once at 10 minutes, if the script is running, then do nothing, otherwise start the working process.
Any insight is highly appreciated since i am not that familiar with bash programming.
#!/bin/bash
# paths
THISPATH="$( cd "$( dirname "$0" )" && pwd )"
# make sure we move in the working directory
cd $THISPATH
# console init path
CONSOLEPATH="$( cd ../../ && pwd )/console.php"
# command line arguments
daemon=0
PHPPATH="/usr/bin/php"
help=0
# flag for binary search
LOOKEDFORPHP=0
# arguments init
while getopts d:p:h: opt; do
case $opt in
d)
daemon=$OPTARG
;;
p)
PHPPATH=$OPTARG
LOOKEDFORPHP=1
;;
h)
help=$OPTARG
;;
esac
done
shift $((OPTIND - 1))
# allow only one process
processesLength=$(ps aux | grep -v "grep" | grep -c $THISPATH/send-campaigns-daemon.sh)
if [ ${processesLength:-0} -gt 2 ]; then
# The process is already running
exit 0
fi
if [ $help -eq 1 ]; then
echo "---------------------------------------------------------------"
echo "| Usage: send-campaigns-daemon.sh |"
echo "| To force PHP CLI binary : |"
echo "| send-campaigns-daemon.sh -p /path/to/php-cli/binary |"
echo "---------------------------------------------------------------"
exit 0
fi
# php executable path, find it if not provided
if [ $PHPPATH ] && [ ! -f $PHPPATH ] && [ $LOOKEDFORPHP -eq 0 ]; then
phpVariants=( "php-cli" "php5-cli" "php5" "php" )
LOOKEDFORPHP=1
for i in "${phpVariants[#]}"
do
which $i >/dev/null 2>&1
if [ $? -eq 0 ]; then
PHPPATH=$(which $i)
fi
done
fi
if [ ! $PHPPATH ] || [ ! -f $PHPPATH ]; then
# Did not find PHP
exit 1
fi
# load options from app
parallelProcessesPerCampaign=3
campaignsAtOnce=10
subscribersAtOnce=300
sleepTime=30
function loadOptions {
local COMMAND="$PHPPATH $CONSOLEPATH option get_option --name=%s --default=%d"
parallelProcessesPerCampaign=$(printf "$COMMAND" "system.cron.send_campaigns.parallel_processes_per_campaign" 3)
campaignsAtOnce=$(printf "$COMMAND" "system.cron.send_campaigns.campaigns_at_once" 10)
subscribersAtOnce=$(printf "$COMMAND" "system.cron.send_campaigns.subscribers_at_once" 300)
sleepTime=$(printf "$COMMAND" "system.cron.send_campaigns.pause" 30)
parallelProcessesPerCampaign=$($parallelProcessesPerCampaign)
campaignsAtOnce=$($campaignsAtOnce)
subscribersAtOnce=$($subscribersAtOnce)
sleepTime=$($sleepTime)
}
# define the daemon function that will stay in loop
function daemon {
loadOptions
local pids=()
local k=0
local i=0
local COMMAND="$PHPPATH -q $CONSOLEPATH send-campaigns --campaigns_offset=%d --campaigns_limit=%d --subscribers_offset=%d --subscribers_limit=%d --parallel_process_number=%d --parallel_processes_count=%d --usleep=%d --from_daemon=1"
while [ $i -lt $campaignsAtOnce ]
do
while [ $k -lt $parallelProcessesPerCampaign ]
do
parallelProcessNumber=$(( $k + 1 ))
usleep=$(( $k * 10 + $i * 10 ))
CMD=$(printf "$COMMAND" $i 1 $(( $subscribersAtOnce * $k )) $subscribersAtOnce $parallelProcessNumber $parallelProcessesPerCampaign $usleep)
$CMD > /dev/null 2>&1 &
pids+=($!)
k=$(( k + 1 ))
done
i=$(( i + 1 ))
done
waitForPids pids
sleep $sleepTime
daemon
}
function daemonize {
$THISPATH/send-campaigns-daemon.sh -d 1 -p $PHPPATH > /dev/null 2>&1 &
}
function waitForPids {
stillRunning=0
for i in "${pids[#]}"
do
if ps -p $i > /dev/null
then
stillRunning=1
break
fi
done
if [ $stillRunning -eq 1 ]; then
sleep 0.5
waitForPids pids
fi
return 0
}
if [ $daemon -eq 1 ]; then
daemon
else
daemonize
fi
exit 0
when starting a script, create a lock file to know that this script is running. When the script finish, delete the lock file. If somebody kill the process while it is running, the lock file remain forever, though test how old it is and delete after if older than a defined value. For example,
#!/bin/bash
# 10 min
LOCK_MAX=600
typedef LOCKFILE=/var/lock/${0##*/}.lock
if [[ -f $LOCKFILE ]] ; then
TIMEINI=$( stat -c %X $LOCKFILE )
SEGS=$(( $(date +%s) - $TIEMPOINI ))
if [[ $SEGS -gt $LOCK_MAX ]] ; then
reportLocking or somethig to inform you
# Kill old intance ???
OLDPID=$(<$LOCKFILE)
[[ -e /proc/$OLDPID ]] && kill -9 $OLDPID
# Next time that the program is run, there is no lock file and it will run.
rm $LOCKFILE
fi
exit 65
fi
# Save PID of this instance to the lock file
echo "$$" > $LOCKFILE
### Your code go here
# Remove the lock file before script finish
[[ -e $LOCKFILE ]] && rm $LOCKFILE
exit 0
from here:
#!/bin/bash
...
echo PARALLEL_JOBS:${PARALLEL_JOBS:=1}
declare -a tests=($(.../find_what_to_run))
echo "${tests[#]}" | \
xargs -d' ' -n1 -P${PARALLEL_JOBS} -I {} bash -c ".../run_that {}" || { echo "FAILURE"; exit 1; }
echo "SUCCESS"
and here you can nick the code for portable locking with fuser
Okay, so i guess i can answer to my own question with a proper answer that works after many tests.
So here is the final version, simplified, without comments/echo :
#!/bin/bash
sleep 2
DIR="$( cd "$( dirname "$0" )" && pwd )"
FILE_NAME="$( basename "$0" )"
COMMAND_FILE_PATH="$DIR/$FILE_NAME"
if [ ! -f "$COMMAND_FILE_PATH" ]; then
exit 1
fi
cd $DIR
CONSOLE_PATH="$( cd ../../ && pwd )/console.php"
PHP_PATH="/usr/bin/php"
help=0
LOOKED_FOR_PHP=0
while getopts p:h: opt; do
case $opt in
p)
PHP_PATH=$OPTARG
LOOKED_FOR_PHP=1
;;
h)
help=$OPTARG
;;
esac
done
shift $((OPTIND - 1))
if [ $help -eq 1 ]; then
printf "%s\n" "HELP INFO"
exit 0
fi
if [ "$PHP_PATH" ] && [ ! -f "$PHP_PATH" ] && [ "$LOOKED_FOR_PHP" -eq 0 ]; then
php_variants=( "php-cli" "php5-cli" "php5" "php" )
LOOKED_FOR_PHP=1
for i in "${php_variants[#]}"
do
which $i >/dev/null 2>&1
if [ $? -eq 0 ]; then
PHP_PATH="$(which $i)"
break
fi
done
fi
if [ ! "$PHP_PATH" ] || [ ! -f "$PHP_PATH" ]; then
exit 1
fi
LOCK_BASE_PATH="$( cd ../../../common/runtime && pwd )/shell-pids"
LOCK_PATH="$LOCK_BASE_PATH/send-campaigns-daemon.pid"
function remove_lock {
if [ -d "$LOCK_PATH" ]; then
rmdir "$LOCK_PATH" > /dev/null 2>&1
fi
exit 0
}
if [ ! -d "$LOCK_BASE_PATH" ]; then
if ! mkdir -p "$LOCK_BASE_PATH" > /dev/null 2>&1; then
exit 1
fi
fi
process_running=0
if mkdir "$LOCK_PATH" > /dev/null 2>&1; then
process_running=0
else
process_running=1
fi
if [ $process_running -eq 1 ]; then
exit 0
fi
trap "remove_lock" 1 2 3 15
COMMAND="$PHP_PATH $CONSOLE_PATH option get_option --name=%s --default=%d"
parallel_processes_per_campaign=$(printf "$COMMAND" "system.cron.send_campaigns.parallel_processes_per_campaign" 3)
campaigns_at_once=$(printf "$COMMAND" "system.cron.send_campaigns.campaigns_at_once" 10)
subscribers_at_once=$(printf "$COMMAND" "system.cron.send_campaigns.subscribers_at_once" 300)
sleep_time=$(printf "$COMMAND" "system.cron.send_campaigns.pause" 30)
parallel_processes_per_campaign=$($parallel_processes_per_campaign)
campaigns_at_once=$($campaigns_at_once)
subscribers_at_once=$($subscribers_at_once)
sleep_time=$($sleep_time)
k=0
i=0
pp=0
COMMAND="$PHP_PATH -q $CONSOLE_PATH send-campaigns --campaigns_offset=%d --campaigns_limit=%d --subscribers_offset=%d --subscribers_limit=%d --parallel_process_number=%d --parallel_processes_count=%d --usleep=%d --from_daemon=1"
while [ $i -lt $campaigns_at_once ]
do
while [ $k -lt $parallel_processes_per_campaign ]
do
parallel_process_number=$(( $k + 1 ))
usleep=$(( $k * 10 + $i * 10 ))
CMD=$(printf "$COMMAND" $i 1 $(( $subscribers_at_once * $k )) $subscribers_at_once $parallel_process_number $parallel_processes_per_campaign $usleep)
$CMD > /dev/null 2>&1 &
k=$(( k + 1 ))
pp=$(( pp + 1 ))
done
i=$(( i + 1 ))
done
wait
sleep ${sleep_time:-30}
$COMMAND_FILE_PATH -p "$PHP_PATH" > /dev/null 2>&1 &
remove_lock
exit 0
Usually, it is a lock file, not a lock path. You hold the PID in the lock file for monitoring your process. In this case your lock directory does not hold any PID information. Your script also does not do any PID file/directory maintenance when it starts in case of a improper shutdown of your process without cleaning of your lock.
I like your first script better with this in mind. Monitoring the PID's running directly is cleaner. The only problem is if you start a second instance with cron, it is not aware of the PID's connect to the first instance.
You also have processLength -gt 2 which is 2, not 1 process running so you will duplicate your process threads.
It seems also that daemonize is just recalling the script with daemon which is not very useful. Also, having a variable with the same name as a function is not effective.
The correct way to make a lockfile is like this:
# Create a temporary file
echo $$ > ${LOCKFILE}.tmp$$
# Try the lock; ln without -f is atomic
if ln ${LOCKFILE}.tmp$$ ${LOCKFILE}; then
# we got the lock
else
# we didn't get the lock
fi
# Tidy up the temporary file
rm ${LOCKFILE}.tmp$$
And to release the lock:
# Unlock
rm ${LOCKFILE}
The key thing is to create the lock file to one side, using a unique name, and then try to link it to the real name. This is an atomic operation, so it should be safe.
Any solution that does "test and set" gives you a race condition to deal with. Yes, that can be sorted out, but you end up write extra code.

Checking for the correct number of arguments

How do i check for the correct number of arguments (one argument). If somebody tries to invoke the script without passing in the correct number of arguments, and checking to make sure the command line argument actually exists and is a directory.
#!/bin/sh
if [ "$#" -ne 1 ] || ! [ -d "$1" ]; then
echo "Usage: $0 DIRECTORY" >&2
exit 1
fi
Translation: If number of arguments is not (numerically) equal to 1 or the first argument is not a directory, output usage to stderr and exit with a failure status code.
More friendly error reporting:
#!/bin/sh
if [ "$#" -ne 1 ]; then
echo "Usage: $0 DIRECTORY" >&2
exit 1
fi
if ! [ -e "$1" ]; then
echo "$1 not found" >&2
exit 1
fi
if ! [ -d "$1" ]; then
echo "$1 not a directory" >&2
exit 1
fi
cat script.sh
var1=$1
var2=$2
if [ "$#" -eq 2 ]
then
if [ -d $var1 ]
then
echo directory ${var1} exist
else
echo Directory ${var1} Does not exists
fi
if [ -d $var2 ]
then
echo directory ${var2} exist
else
echo Directory ${var2} Does not exists
fi
else
echo "Arguments are not equals to 2"
exit 1
fi
execute it like below -
./script.sh directory1 directory2
Output will be like -
directory1 exit
directory2 Does not exists
You can check the total number of arguments which are passed in command line with "$#"
Say for Example my shell script name is hello.sh
sh hello.sh hello-world
# I am passing hello-world as argument in command line which will b considered as 1 argument
if [ $# -eq 1 ]
then
echo $1
else
echo "invalid argument please pass only one argument "
fi
Output will be hello-world

Resources