Run random command in bash script - linux

I would like to randomly select one random command in a bash script.
I have tried commands like
echo $(( $RANDOM % 12 ))
But they work in the UNIX command line but not in my bash script.
Anyone have a solution for my problem?

-- Updated answer as per discussion in comments --
For Bash Users:
Code given in question works fine. Just don't forget to set permissions to +x (grants executable permission to all). Eg: chmod +x myscript.sh
Caution: Don't set RANDOM to a value. It looses its special properties.
Bash man page says-
RANDOM Each time this parameter is referenced, a random integer
between 0 and 32767 is generated. The sequence of random numbers may
be initialized by assigning a value to RANDOM. If RANDOM is unset, it
loses its special properties, even if it is sub- sequently reset.
For dash Users:
sh in ubuntu is a symbolic link to dash.
dash does not support RANDOM variable, it is just another variable.
If you are bound to use sh, you can use date function to generate
pseudo randoms only if your script is not time-dependent. (if your
script runs at strict intervals of time, it might produce same values)
RANDOM=`date '+%s'`
echo $(( $RANDOM % 12 )) # To generate random numbers less than 12
In General:
If you want to generate true random numbers use this-
dd if=/dev/urandom count=1 2> /dev/null | cksum | cut -f1 -d" "
Source: http://www.linuxquestions.org/questions/programming-9/shell-script-random-variable-4088/

Create the shell script test.sh
chmod +x test.sh
content of test.sh
#!/bin/bash
echo $(( $RANDOM % 12 ))
Run it via
bash test.sh
or
./test.sh

Related

Date command as a variable in a bash script. Needs to be invoked each time instead of during variable declaration

I have a bash script and at certain points I am using echo to put some messages in a log file. The problem that I have is related to the DATE variable which will be static throughout the entire execution of the script.
I have this basic script below to illustrate the problem:
#!/bin/bash
DATE=`date +"%Y-%m-%dT%H:%M:%S%:z"`
echo "script started at $DATE"
echo "doing something"
sleep 2
echo "script finished at $DATE"
If I execute this script, the output of the $DATE variable is the same in both lines. Is there some bash magic that could nicely resolve this without having to replace $DATE with the command itself on each line?
Thanks in advance
Newer versions of the bash/printf builtin have support for generating datetime stamps without the need to spawn a subprocess to call date:
$ builtin printf --help
...snip...
Options:
-v var assign the output to shell variable VAR rather than
display it on the standard output
...snip...
In addition to the standard format specifications described in printf(1),
printf interprets:
%b expand backslash escape sequences in the corresponding argument
%q quote the argument in a way that can be reused as shell input
%(fmt)T output the date-time string resulting from using FMT as a format
string for strftime(3)
... snip ...
Instead of spawning a subprocess to call date, eg:
logdt=`date +"%Y-%m-%dT%H:%M:%S:%z"`
The same can be accomplished via printf -v by wrapping the desired format in %(...)T, eg:
printf -v logdt '%(%Y-%m-%dT%H:%M:%S:%z)T'
NOTE: assuming %:z should be :%z
Assuming you'll be tagging a lot of lines with datetime stamps then the savings from eliminating the subproces date calls could be huge.
Running a test of 1000 datetime stamp generations:
$ time for ((i=1;i<=1000;i++)); do { printf -v logdt '%(...)T' | logdate=$(date ...) }; done
Timings for printf -v logdt '%(...)T':
real 0m0.182s # ~130 times faster than $(date ...)
user 0m0.171s
sys 0m0.000s
Timings for logdt=$(date ...):
real 0m24.443s # ~130 times slower than printf -v
user 0m5.533s
sys 0m16.724s
With bash version 4.3+ , you can use the builtin printf to format datetimes. -1 below is a magic value that means "now".
#!/bin/bash
datefmt='%Y-%m-%dT%H:%M:%S%z'
printf "script started at %($datefmt)T\n" -1
echo "doing something"
sleep 2
printf "script finished at %($datefmt)T\n" -1
bash didn't recognize %:z for me.
This can help you:
#!/bin/bash
echo "script started at $(date +'%Y-%m-%dT%H:%M:%S%:z')"
echo "doing something"
sleep 2
echo "script finished at $(date +'%Y-%m-%dT%H:%M:%S%:z')"
You might want to create an alias if calling the full command looks clumsy to you.

Increment the title of files output by a command in a shell script

I made this simple bash script to take a full-screen screenshot and save it to the pictures folder
#!/usr/bin/bash
xfce4-screenshooter -f -s /home/rgcodes/Pictures/Screenshot_$scrshotcount.png
let "scrshotcount++"
...which runs into a problem. scrshotcount is a global variable I defined in /etc/environment to be incremented every time the script runs. However, the script fails to increment the variable globally, and causes the script to just overwrite the previous screenshot. Searches on Google and Stack Overflow revealed that the problem isn't straightforward at all (something about child shells being unable to change variables for parents), and finding some other method would be better.
Here's my question. How do we append numbers (in ascending order) to the screenshots the script throws out so that they are saved just like those taken on Windows?(Windows auto-suffixes matching filenames, rather than overwriting them, so all Screenshots have the same name 'Screenshot' and the number of times the screenshot command has been used.)
I am using #erikMD's method as a temporary stop-gap for now.
In addition to the excellent advice about using a date instead of a counter, here's a way to use a counter :/
dir=$HOME/Pictures
# find the current maximum value
current_max=$(
find "$dir" -name Screenshot_\*.png -print0 \
| sort -z -V \
| tail -z -n 1
)
if [[ ! $current_max =~ _([0-9]+)\.png ]]; then
echo "can't find the screenshot with the maximum counter value" >&2
exit 1
fi
# increment it
counter=$(( 1 + ${BASH_REMATCH[1]} ))
# and use it
xfce4-screenshooter -f -s "$dir/Screenshot_${counter}.png"
You'll have to manually create the Screenshot_1.png file.
#rgcodes below is a script that will capture screenshots with a numeric count indicator per your original post. (tested it on Ubuntu 20.04)
Script contents:
#!/bin/bash
set -uo pipefail
# add source file and line number to xtrace output
# i.e. when running: bash -x ./your_script_name.sh
export PS4='+(${BASH_SOURCE}:${LINENO}): ${FUNCNAME[0]:+${FUNCNAME[0]}(): }'
capture_screenshot() {
local output_dir="${1:-/tmp/screenshot}"
local img_name="${2:-Screenshot}"
local img_ext="${3:-png}"
# create output directory (if not already existing)
mkdir -p "${output_dir}"
# get the last image in the sorted ls output
local latest_png=$(tail -n 1 \
<(sort -n -t _ -k 2 \
<(ls ${output_dir}/*.${img_ext} 2> /dev/null)))
# use the latest image to determine img_cnt value for next image
local img_cnt=0
if [[ ${latest_png} =~ _([0-9]+)\.png ]]; then
img_cnt=$((1+${BASH_REMATCH[1]}))
elif [[ ${latest_png} =~ ${img_name}.${img_ext} ]] ; then
img_cnt=1
fi
# build path to output image
local img_path="${output_dir}/${img_name}_${img_cnt}.${img_ext}"
# remove count from output image path if count == 0
if [[ "${img_cnt}" -eq "0" ]] ; then
img_path="${output_dir}/${img_name}.${img_ext}"
fi
xfce4-screenshooter -f -s "${img_path}"
}
capture_screenshot "$#"
The uses the following as defaults, but you can change them to meet your requirements.
output directory for screenshots:
/tmp/screenshot
base screenshot image name:
Screenshot
screenshot file extension:
.png
The script will attempt to create the output directory if it does not already exist (subject to user permission for creation). Below is a sample usage.
Prior to initial script execution, the output directory does not exist:
$ ls screenshot
$
Initial execution (directory is created and Screenshot.png created:
$ ./script.sh
$ ls /tmp/screenshot/
Screenshot.png
Subsequent executions:
$ ./script.sh
$ ls /tmp/screenshot/
Screenshot_1.png Screenshot.png
$ ./script.sh
$ ls /tmp/screenshot/
Screenshot_1.png Screenshot_2.png Screenshot.png
Indeed, as suggested by #j_b in the comments, you should definitely give a try to using a timestamp with the command date +"$format".
FTR, the same idea is implemented here in this project of a gnome-screenshot bash wrapper
(disclaimer: I am the author of this repo).
Example command:
date "+%Y-%m-%d_%H-%M-%S"
↓
2021-07-29_19-13-30
So the overall script could just be something like:
#!/usr/bin/env bash
xfce4-screenshooter -f -s "$HOME/Pictures/Screenshot_$(date "+%Y-%m-%d_%H-%M-%S").png"
(Note that I added missing double-quotes, and modified your shebang, as /usr/bin/env bash is more portable than /bin/bash or /usr/bin/bash.)

Bash variable expansion can't be combined with time command

I was writing a small shell script in bash to measure the speed of several executables using the time command. Because I wanted to time several different programs, to avoid repeating myself I saved the time command and the format string I wanted and tried to use variable expansion (testing the performance of ls as an example):
#!/usr/bin/env bash
timer="/usr/bin/time -f 'elapsed time: %E' --"
$timer ls 1>/dev/null
The time command is supposed to use the -- to separate its options (in this case a format string) from the command it's supposed to time. However, in this script time insists on trying to execute my format string and fails with
/usr/bin/time cannot run time:: No such file or directory
Command exited with non-zero status 127
'elapsed
But if I put the whole command on one line with no variable expansion it correctly recognizes the format string and executes ls:
#!/usr/bin/env bash
/usr/bin/time -f 'elapsed time: %E' -- ls 1>/dev/null
produces "elapsed time: 0:00.00"
I ended up just typing out the whole command on each line to get it to run, but I was curious if anyone could explain why variable expansion doesn't work here.
The data in $timer is split on $IFS (spaces), and used as separate arguments. It is not expanded and then evaluated as bash code.
Your command $timer ls is therefore equivalent to:
"/usr/bin/time" "-f" "'elapsed time:" "%E'" "--" "ls"
The correct way to do this in your case is using a function:
timer() {
/usr/bin/time -f 'elapsed time: %E' -- "$#"
}
timer ls
PS: This problem is automatically pointed out by shellcheck.
You need to use a BASH array to store command line:
timer=(/usr/bin/time -f 'elapsed time: %E' --)
"${timer[#]}" ls

Triple nested quotations in shell script

I'm trying to write a shell script that calls another script that then executes a rsync command.
The second script should run in its own terminal, so I use a gnome-terminal -e "..." command. One of the parameters of this script is a string containing the parameters that should be given to rsync. I put those into single quotes.
Up until here, everything worked fine until one of the rsync parameters was a directory path that contained a space. I tried numerous combinations of ',",\",\' but the script either doesn't run at all or only the first part of the path is taken.
Here's a slightly modified version of the code I'm using
gnome-terminal -t 'Rsync scheduled backup' -e "nice -10 /Scripts/BackupScript/Backup.sh 0 0 '/Scripts/BackupScript/Stamp' '/Scripts/BackupScript/test' '--dry-run -g -o -p -t -R -u --inplace --delete -r -l '\''/media/MyAndroid/Internal storage'\''' "
Within Backup.sh this command is run
rsync $5 "$path"
where the destination $path is calculated from text in Stamp.
How can I achieve these three levels of nested quotations?
These are some question I looked at just now (I've tried other sources earlier as well)
https://unix.stackexchange.com/questions/23347/wrapping-a-command-that-includes-single-and-double-quotes-for-another-command
how to make nested double quotes survive the bash interpreter?
Using multiple layers of quotes in bash
Nested quotes bash
I was unsuccessful in applying the solutions to my problem.
Here is an example. caller.sh uses gnome-terminal to execute foo.sh, which in turn prints all the arguments and then calls rsync with the first argument.
caller.sh:
#!/bin/bash
gnome-terminal -t "TEST" -e "./foo.sh 'long path' arg2 arg3"
foo.sh:
#!/bin/bash
echo $# arguments
for i; do # same as: for i in "$#"; do
echo "$i"
done
rsync "$1" "some other path"
Edit: If $1 contains several parameters to rsync, some of which are long paths, the above won't work, since bash either passes "$1" as one parameter, or $1 as multiple parameters, splitting it without regard to contained quotes.
There is (at least) one workaround, you can trick bash as follows:
caller2.sh:
#!/bin/bash
gnome-terminal -t "TEST" -e "./foo.sh '--option1 --option2 \"long path\"' arg2 arg3"
foo2.sh:
#!/bin/bash
rsync_command="rsync $1"
eval "$rsync_command"
This will do the equivalent of typing rsync --option1 --option2 "long path" on the command line.
WARNING: This hack introduces a security vulnerability, $1 can be crafted to execute multiple commands if the user has any influence whatsoever over the string content (e.g. '--option1 --option2 \"long path\"; echo YOU HAVE BEEN OWNED' will run rsync and then execute the echo command).
Did you try escaping the space in the path with "\ " (no quotes)?
gnome-terminal -t 'Rsync scheduled backup' -e "nice -10 /Scripts/BackupScript/Backup.sh 0 0 '/Scripts/BackupScript/Stamp' '/Scripts/BackupScript/test' '--dry-run -g -o -p -t -R -u --inplace --delete -r -l ''/media/MyAndroid/Internal\ storage''' "

Concatenate strings inside bash script (different behaviour from shell)

I'm trying some staff that is working perfectly when I write it in the regular shell, but when I include it in a bash script file, it doesn't.
First example:
m=`date +%m`
m_1=$((m-1))
echo $m_1
This gives me the value of the last month (actual minus one), but doesn't work if its executed from a script.
Second example:
m=6
m=$m"t"
echo m
This returns "6t" in the shell (concatenates $m with "t"), but just gives me "t" when executing from a script.
I assume all these may be answered easily by an experienced Linux user, but I'm just learning as I go.
Thanks in advance.
Re-check your syntax.
Your first code snippet works either from command line, from bash and from sh since your syntax is valid sh. In my opinion you probably have typos in your script file:
~$ m=`date +%m`; m_1=$((m-1)); echo $m_1
4
~$ cat > foo.sh
m=`date +%m`; m_1=$((m-1)); echo $m_1
^C
~$ bash foo.sh
4
~$ sh foo.sh
4
The same can apply to the other snippet with corrections:
~$ m=6; m=$m"t"; echo $m
6t
~$ cat > foo.sh
m=6; m=$m"t"; echo $m
^C
~$ bash foo.sh
6t
~$ sh foo.sh
6t
Make sure the first line of your script is
#!/bin/bash
rather than
#!/bin/sh
Bash will only enable its extended features if explicitly run as bash. If run as sh, it will operate in POSIX compatibility mode.
First of all, it works fine for me in a script, and on the terminal.
Second of all, your last line, echo m will just output "m". I think you meant "$m"..

Resources