How can I “fill in the blanks” for a command in a script from user input? - linux

I’m trying to build a script that asks for a time clock number and a DC number from the user running the script, which I’m intending to fill in the Xs for
/u/applic/tna/shell/tc_software_update.sh tmcxx.s0xxxx.us REFURBISHED
However, I am stumped as to how to have the user’s input fill in those Xs on that command within the script. This script is in its earliest stages so it’s very rough right now lol. Thank you for responding. Here’s the scripts skeleton I’m working on:
#!/bin/bash
#This server is intended to speed up the process to setup timeclocks from DC tickets
#Defines time clock numbers
timeclocks="01|02|03|04|05|06|07|08|09|10|11|12|13|14|15|16|17|18|19|20|21|22|23|24|25|26|27|28|29|30|31|32|33|34|35"
#Defines DC number
echo “What is the DC number?”
read dc
#Defines TMC number
echo "What is the Time Clock number?"
read number
if $number == $timeclocks && $dc == ???; then
/u/applic/tna/shell/tc_software_update.sh tmcxx.s0xxxx.us REFURBISHED

Do you mean invoking $ /u/applic/tna/shell/tc_software_update.sh tmc${number}.s0${dc}.us REFURBISHED?
Consider the following snippet:
[test.sh]
read x
read y
echo "x=${x}, y=${y}"
$ sh test.sh
5
4
x=5, y=4
Further on, you can use command line arguments ($1, $2 etc.) instead of the user input.

Modelling this on your script:
timeclocks=( {1..35} )
printf '%s' "DC number: "; read dc
printf '%s' "Time Clock number: "; read tmc
tmc=$( printf '%02d' "$tmc" )
dc=$( printf '%04d' "$dc" )
tmc_valid=$( for t in ${timeclocks[#]}; do \
[[ $tmc -eq $t ]] && echo true && break; \
done )
[[ "$tmc_valid" = "true" && "$dc" = "???" ]] && \
/u/applic/tna/shell/tc_software_update.sh tmc${tmc}.s0${dc}.us REFURBISHED

Related

Shell: Pass function with arguments as function argument

I'm working on a shell program to automatise my Arch (mandatory btw) installation. To make it more interactive, I've built the following function:
# READYN
# ARGS:
# - Yes/no question
# - Command to run if yes
# - Command to run if no
#
# Prompts the user with a yes/no question (with precedence for yes) and
# run an order if the answer is yes or another if it's no.
readyn () {
while :
do
local yn;
printf "%s? [Y/n]: " "$1";
read yn;
if [[ "$yn" =~ ^([yY][eE][sS]|[yY])?$ ]]; then
$2;
break;
elif [[ "$yn" =~ ^([nN][oO]|[nN])+$ ]]; then
$3;
break;
fi
done
}
I've succeeded in passing an "echo Hello World!" as an argument and having it run. I've also been able to pass another function. For example:
yayprompt () {
printf "yay is required to install %s.\n" "$1"
readyn "Install yay, the AUR manager" "yayinstall" ""
}
This calls yayinstall if yes and does nothing if no.
My problem comes with more complex functions, which are passed as arguments but are either not recognised or run when they're not supposed to. The problem comes with the following function:
# MANAGEPGK
# ARGS:
# - Package name
# - Package variable
# - Yay required
#
# Checks if the package is added to the pkglist to either add or remove it.
# If yay is required to install it, it prompts the user whether they wish
# to install yay or don't install the package (if yay is not installed).
# This functions DOES NOT prompt any installation options on the user. To
# do this, use PROMPTPKG.
managepkg () {
local pkgvar=$2
if [ $pkgvar == 0 ]; then
if [ $3 == 1 ] && [ $yay == 0 ]; then
yayprompt;
fi
if [ $3 == 0 ] || [ $yay == 1 ]; then
addpkg "$1";
pkgvar=1;
fi
else
rmpkg "$1";
pkgvar=0;
fi
echo "$pkgvar";
}
For it to work properly, it has to (or at least I've had to) be called like this:
dewm_cinnamon=$(managepkg cinnamon $dewm_cinnamon 0)
Now, I'm trying to pass it as an argument to readyn, but I'm having these outputs depending on the format (I'm always answering yes as empty string:
Simple quotes:
readyn "Install gaps" \
'dewm_i3gaps=$(managepkg i3-gaps $dewm_i3gaps 0)' \
'dewm_i3=$(managepkg i3-wm $dewm_i3 0)';
Install gaps? [Y/n]:
./architup.sh: line 341: dewm_i3gaps=$(managepkg: command not found
Double quotes:
readyn "Install gaps" \
"dewm_i3gaps=$(managepkg i3-gaps $dewm_i3gaps 0)" \
"dewm_i3=$(managepkg i3-wm $dewm_i3 0)";
Install gaps? [Y/n]:
./architup.sh: line 341: dewm_i3gaps=1: command not found
Dollar enclosed: (This one runs both commands as seen in cat pkglist)
readyn "Install gaps" \
$(dewm_i3gaps=$(managepkg i3-gaps $dewm_i3gaps 0)) \
$(dewm_i3=$(managepkg i3-wm $dewm_i3 0));
Install gaps? [Y/n]:
Install compton? [Y/n]: ^C
Documents/Repositories/architup took 5s
➜ cat pkglist
i3-gaps
i3-wm
What syntax should I use to have readyn run only one command based on the user input?
Thank you!
Function arguments are just strings. A better design IMHO is to simply have readyn return true (zero) for "yes" and false otherwise, and have the calling code implement any conditional logic based on that.
readyn () {
read -p "$#"
case $REPLY in
[Yy] | [Yy][Ee][Ss]) return 0;;
esac
return 1
}
readyn "Are you ready San Antonio?" &&
rock and roll
if readyn "Let me hear you say yeah"; then
echo "Let's go!"
else
echo "If you feel mellow, get outta here"
fi
(With apologies to rock concerts everywhere,)

Making a program that takes user input for a number 1-7 and then displays a command according to the number the user chooses. [Bash - CentOS]

As the title says, I'm trying to make a program that has a user input a number 1-7, then displays the appropriate command for each number.
The problem I'm having is finding a good way to set each number to a command.
At first, I thought about doing something like this.
OSI=$(uname -a)
echo $OSI
But the problem with that is actually implementing it into a loop. Let's say the user is prompted like so:
"Enter a number:"
The user enters the number 1, and number 1 is the OSI. Well if a user picks the number 2, it needs to display a different command and so forth.
This is a little bit too complicated for a beginner like myself. I've read through forums and different posts, but I cannot figure out the right commands to make this happen.
I tried doing something like this and it failed miserably:
#!/bin/bash
read -p "Enter a number:" n1 n2 n3 n4 n5 n6 n7
if n1=1; then
uname - a
else n2=2; "different command"
fi
I realize I'm completely garbage at bash. I'm not asking for anyone to solve this, just give me some pointers in a way that makes sense to me.
Thanks.
Give this tested version a try:
#!/bin/bash --
printf "menu items:\n 1) uname -a\n 2) date\n q) exit\n"
read -p "Enter your choice: " response
if [ -z "$response" ] ; then
printf "Choice invalid\n"
exit 1
fi
if [ "$response" = q ] ; then
exit 0
fi
if [ "$response" = 1 ] ; then
uname -a
elif [ "$response" = 2 ] ; then
date
else
printf "Choice invalid\n"
fi
As written by #EdMorton case is a better option.

0999: Value too great for base (error token is "0999")

This is a shortened-version of a script for reading 8mm tapes from a EXB-8500 with an autoloader (only 10 tapes at a time maximum) attached. It dd's in tape data (straight binary) and saves it to files that are named after the tape's 4-digit number (exmaple D1002.dat) in both our main storage and our backup. During this time it's logging info and displaying its status in the terminal so we can see how far along it is.
#!/bin/bash
echo "Please enter number of tapes: [int]"
read i
j=1
until [ $i -lt $j ]
do
echo "What is the number of tape $j ?"
read Tape_$j
(( j += 1 ))
done
echo "Load tapes into the tower and press return when the drive is ready"
read a
j=1
until [ $i -lt $j ]
do
k="Tape_$j"
echo "tower1 $j D$(($k)) `date` Begin"
BEG=$j" "D$(($k))" "`date`" ""Begin"
echo "tower1 $j D$(($k)) `date` End"
END=$j" "D$(($k))" "`date`" ""End"
echo "$BEG $END"
echo "$BEG $END"
sleep 2
(( j += 1 ))
done
echo "tower1 done"
Everything was hunky-dory until we got under 1000 (startig at 0999). Error code was ./tower1: 0999: Value too great for base (error token is "0999"). Now I already realize that this is because the script is forcing octal values when I type in the leading 0, and I know I should insert a 10# somewhere in the script, but the question is: Where?
Also is there a way for me to just define Tape_$j as a string? I feel like that would clear up a lot of these problems
To get the error, run the script, define however many tapes you want (at least one, lol), and insert a leading 0 into the name of the tape
EXAMPLE:
./test
Please enter number of tapes: [int]
1
What is the number of tape 1?
0999
./test: 0999: Value too great for base (error token is "0999")
You don't want to use $k as a number, but as a string. You used the numeric expression to evaluate a variable value as a variable name. That's very bad practice.
Fortunately, you can use variable indirection in bash to achieve your goal. No numbers involved, no error thrown.
echo "tower1 $j ${!k} `date` Begin"
BEG=$j" "D${!k}" "`date`" ""Begin"
And similarly in other places.

Bash split a space-delimited string into a variable number of substrings

Suppose I have two space-delimited strings in my bash script, which are
permitted_hosts="node1 node2 node3"
and
runs_list="run1 run2 run3 run4 run5"
These respectively represent the list of permitted hosts and the list of runs to execute. So, I need to run each of the runs in $runs_list on 1 of the hosts in $permitted_hosts.
What I'd like to do is divide $runs_list into $N substrings, where $N is the number of elements in $permitted_hosts and where each of the $N substrings is mapped to a different element in $permitted_hosts.
If that's confusing, then consider this concrete workaround solution. For the exact given values of $permitted_hosts and $runs_list above, the following bash script checks the current host, and if the current host is in $permitted_hosts, then it launches the runs in $runs_list that are associated with the current host. Of course, this script doesn't use the variables $permitted_hosts and $runs_list, but it achieves the desired effect for the given example. What I'm really trying to do is modify the code below so that I can modify the values of variables $permitted_hosts and $runs_list and it will work appropriately.
#!/bin/bash
hostname=$(hostname)
if [ "$hostname" == "node1" ]; then
runs="run1 run2"
elif [ "$hostname" == "node2" ]; then
runs="run3 run4"
elif [ "$hostname" == "node3" ]; then
runs="run5"
else
echo "ERROR: Invoked on invalid host ('$hostname')! Aborting."
exit 0
fi
for run in $runs; do
./launch $run
done
So, firstly — instead of space-delimited strings, you should probably use arrays:
permitted_hosts=(node1 node2 node3)
runs_list=(run1 run2 run3 run4 run5)
If you have to start out with space-delimited strings, you can at least convert them to arrays:
permitted_hosts=($permitted_hosts_str)
runs_list=($runs_list_str)
That out of the way . . . basically you have two steps: (1) convert the hostname into an integer representing its position in permitted_hosts:
hostname="$(hostname)"
num_hosts="${#permitted_hosts[#]}" # for convenience
host_index=0
while true ; do
if [[ "${permitted_hosts[host_index]}" = "$hostname" ]] ; then
break
fi
(( ++host_index ))
if (( host_index > num_hosts )) ; then
printf 'ERROR: Invoked on invalid host ('%s')! Aborting.\n' "$hostname" >&2
exit 1
fi
done
# host_index is now an integer index into permitted_hosts
and (2) convert this integer into an appropriate subset of runs_list:
num_runs="${#runs_list[#]}" # for convenience
for (( run_index = host_index ; run_index < num_runs ; run_index += num_hosts )) ; do
./launch "${runs_list[run_index]}"
done
So, for example, if you have H hosts, then host #0 will launch run #0, run #H, run #2H, etc.; host #1 will launch run #1, run #H+1, run #2H+1, etc.; and so on.

if: Expression syntax Error in csh - not to accept other string than the only string it must accept

There's this daemon with, for ex. 5 types in one script. Now, i want to be able to start/stop it by specifying the number of the daemon(to start one by one), OR specify "all" (to start in bulk).
The format: (runscript) (commandName) (daemon # or "all")
Need to satisfy two conditions, when the user inputs: (1) correctly (either by number or "all) OR
(2) incorrectly (either inputted num is greater than $count or all other string than "all").
All conditions are already achieved except for one, if the user inputs other string than "all"
Sample code:
case 'startDaemon': #commandName
set count = 5
if ($#argv == 2 && $2 == all) then
echo "correct, do this"
else if ($#argv == 2 && $2 < $count) then
echo "correct too, do this"
else if ($#argv == 2 && ($2 != all || $2 >= $count)) then
echo "Incorrect parameter: specify daemon # less than $count or 'all' to start all."
else
echo "Please use: $0(runscript) $1(commandname) (daemon # or all)"
whenever I type: (runscript) startDaemon hello, for example, error shows:
if: Expression syntax
When it should have gone to the 3rd condition. Please help and kindly point out if the prob is in the conditions or logical operators or whatever. Thanks in advance
PS. Im using csh. The script given to me is in csh, so yep.
The immediate problem is the comparison $2 < $count, which is invalid when $count contains a string.
Here is a working solution:
#!/bin/csh
set count = 5
if ($#argv == 2) then
if ($2 == all) then
echo "correct, do this"
else if (`echo $2 | grep '^[0-9]*$'`) then
if ($2 < $count) then
echo "correct too, do this"
else
echo "Number must be less than 5"
endif
else
echo "Incorrect parameter: specify daemon # less than $count or 'all' to start all."
endif
else
echo "Please use: $0(runscript) $1(commandname) (daemon # or all)"
endif

Resources