Shell variable value passing to subshell? - shell

I am writing script in shell and it's something like:
temp=0
while true; do
case "a" in
a) temp=5; ;;
esac
break
done | echo "$temp"
( temp value is condition for if in subshell and | (pipe) is needed as stdout redirect to stdin)
and I need in temp 5 but I got 0 in echo. Any way to preserve value inside case (posixly correct without tempfile)?

Do you want it like this? Prints 5 and 0.
#!/bin/sh
temp=0
while true; do
case "a" in
a) temp=5; echo "$temp" ;;
esac
break
done | cat
echo "temp unchanged because of subshell $temp"
Would this help you, filtering out your status?
I still don't get why you need a pipe and how you expect the other command you pipe into to react on a status. It's just not the way it works. You evaluate conditions in your script and call commands accordingly.
#!/bin/sh
filter(){
data=''
while read line; do
case "$line" in
temp=5) temp=5 ;;
*) data=$(printf '%s\n' "$data"; printf '%s\n' "$line") ;;
esac
done
if [ "$temp" -eq 5 ]; then
echo "do this 5 with data"
else
echo "do that other with data"
echo "$data"
fi
}
temp=0
while true; do
case "a" in
a) temp=5; printf 'temp=%s\n' "$temp"; for i in $(seq 1 30); do echo "30 lines"; done ;;
esac
break
done | filter
echo "temp unchanged because of subshell $temp"

Related

Bash: How to check in array check if input was entered twice?

The array variable ARRAY_ITEMS adding input from the user.
Here my script.
#!/bin/bash
var1=$(echo -e "Adding $INPUT to array.")
while true; do
printf "\n
A B C\n"
echo -e "This is the array: $ARRAY_ITEMS: "
read -p "Input: " INPUT
case "$INPUT" in
A) echo $var1 ; ARRAY_ITEMS+=$INPUT ;;
B) echo $var1 ; ARRAY_ITEMS+=$INPUT ;;
C) echo $var1 ; ARRAY_ITEMS+=$INPUT ;;
*) echo -e "Whoopsy! Invalid input." ;;
esac
done
The Output looks like this:
A B C This is the array: ABCAAABCAAA:
Input: D Whoopsy! Invalid input.
How does my script have to look like so that the following output appears for an input that has already been entered?
A B C
This is the array: ABC:
Input: A
ERROR. A is already selected.
Use pattern-matching to see if the input is already in the string.
items=
while true; do
printf "\n
A B C\n"
echo "This is the array: $items"
read -p "Input: " input
case "$INPUT" in
A|B|C) if [[ $items = *$input* ]]; then
echo "Error, $input is already in $items"
else
items+=$input
fi ;;
*) echo "Whoopsy! Invalid input." ;;
esac
done
If the input could be something more than a single letter, you'll need to use an array and resign yourself to walking the array, one element at a time, to compare them to the input. Something like
items=()
while true; do
...
found=0
case $input in
A|B|C) for x in "${items[#]}"; do
if [[ $x == $input ]]; then
echo "Error, already used $input"
found=1
break
fi
done
;;
*) echo "Error, invalid input" ;;
esac
(( found )) || items+=("$input")
done

Print bash script argument passed into a new script generated by your current script

Title might be confusing -- I'm writing up a bash script that creates another bash script in the same relative directory, and I want to print an argument passed into the second script.
Layout:
#!/bin/bash
set -e
while [[ $# -gt 1 ]]; do
key="$1"
if [[ ! "$2" =~ ^-[^-].* ]];
case $key in
--arg1 | -t)
arg1=$2
shift #shift past argument
;;
--arg2 | -k)
arg2=$2
shift #shift past argument
;;
--arg3 | -i)
arg3=$2
break
;;
*)
;;
esac
fi
done
cat <<'EOF' > secondScript.sh
#!/bin/bash
set -e
while [[ $# -gt 1 ]]; do
key="$1"
if [[ ! "$2" =~ ^-[^-].* ]]; then # don't gobble up next key if this key doesn't have a value
case $key in
--finalArg1 | -t)
finalArg1=$2
shift #shift past argument
;;
--finalArg2 | -k)
finalArg2=$2
shift #shift past argument
;;
--finalArg3 | -i)
finalArg3=$2
break
;;
*)
;;
esac
fi
done
echo ${finalArg1}
EOF
chmod +x secondScript.sh
./secondScript.sh --finalArg1 {arg1} --finalArg2 {arg2} --finalArg3 {arg3}
so the end result is arg1 printed to the console. Instead, this code just prints
{finalArg1}. Any way to do this?
Ah nvm this was just an oversight -- Forgot to add $ to substitute the variables on the second script call. Should look like this:
./secondScript.sh --finalArg1 ${arg1} --finalArg2 ${arg2} --finalArg3 ${arg3}

email shell script part 2

I'm creating a shell script that takes in user input and text's people using the mail function. I am looking to make it more advanced. Right now it just text's one person at a time, I want it to have the ability to text multiple people or even everyone with a user input of 'All'.
#!/bin/sh
# Prefix the numbers with something
number_Joe=8881235555
number_Bob=8881235556
echo "Who do you want to text?:(i.e. Joe, Bob, etc)"
read name
echo "What do you want to say?:"
read quote
# Remove any dangerous characters that the user enters
sanitized=$(printf "%s" "$name" | tr -cd 'a-zA-Z')
#Look up by evaluating e.g. "number=$number_Joe"
eval "number=\$number_$sanitized"
if [ "$number" ]
then
echo "texting $name ($number) with $quote"
printf "%s\n" "$quote" | mailx -s "Text Message via email" "$number#txt.att.net"
else
echo "Unknown user"
exit 1
fi
Also, is there a cleaner method of bringing in a external txt file that houses the numbers instead of the script?
(note: we still have bash <4, thus why I'm not using a associative array)
Here's a rewrite.
Should work fine in bash3.
#!/bin/bash
# Prefix the numbers with something
names=()
names+=(Joe); numberJoe=8881235555
names+=(Bob); numberBob=8881235556
domain=txt.att.example.com
usage () {
echo "usage: $(basename $0) names message ..."
echo "where: names is a comma-separated list of names (no spaces)"
echo
echo "example: $(basename $0) Jim,Fred hello lads, this is my message"
}
while getopts ":hl" opt; do
case $opt in
h) usage; exit ;;
l) IFS=,; echo "known names: ${names[#]}"; exit ;;
esac
done
shift $((OPTIND - 1))
if (( $# < 2 )); then
usage
exit
fi
IFS=, read -ra usernamelist <<<"$1"
shift
message="$*"
# validate names
namelist=()
for name in "${usernamelist[#]}"; do
if [[ " ${names[#]} " == *" $name "* ]]; then
namelist+=("$name")
else
echo "unknown name: $name" >&2
fi
done
if (( ${#namelist[#]} == 0 )); then
echo "no valid names given" >&2
exit 1
fi
# generate the recipient list
echo "texting '$message' to:"
recipients=()
for name in "${namelist[#]}"; do
numvar="number$name"
echo " $name -> ${!numvar}"
recipients+=( "${!numvar}#$domain" )
done
# send it
printf "%s\n" "$message" | mailx -s "Text Message via email" "$(IFS=,; echo "${recipients[*]}")"

Converting Bash command line options to variable name

I am trying to write a bash script that takes in an option.
Lets call these options A and B.
In the script A and B may or may not be defined as variables.
I want to be able to check if the variable is defined or not.
I have tried the following but it doesn't work.
if [ ! -n $1 ]; then
echo "Error"
fi
Thanks
The "correct" way to test whether a variable is set is to use the + expansion option. You'll see this a lot in configure scripts:
if test -s "${foo+set}"
where ${foo+set} expands to "set" if it is set or "" if it's not. This allows for the variable to be set but empty, if you need it. ${foo:+set} additionally requires $foo to not be empty.
(That $(eval echo $a) thing has problems: it's slow, and it's vulnerable to code injection (!).)
Oh, and if you just want to throw an error if something required isn't set, you can just refer to the variable as ${foo:?} (leave off the : if set but empty is permissible), or for a custom error message ${foo:?Please specify a foo.}.
You did not define how these options should be passed in, but I think:
if [ -z "$1" ]; then
echo "Error"
exit 1
fi
is what you are looking for.
However, if some of these options are, err, optional, then you might want something like:
#!/bin/bash
USAGE="$0: [-a] [--alpha] [-b type] [--beta file] [-g|--gamma] args..."
ARGS=`POSIXLY_CORRECT=1 getopt -n "$0" -s bash -o ab:g -l alpha,beta:,gamma -- "$#"`
if [ $? -ne 0 ]
then
echo "$USAGE" >&2
exit 1
fi
eval set -- "$ARGS"
unset ARGS
while true
do
case "$1" in
-a) echo "Option a"; shift;;
--alpha) echo "Option alpha"; shift;;
-b) echo "Option b, arg '$2'"; shift 2;;
--beta) echo "Option beta, arg '$2'"; shift 2;;
-g|--gamma) echo "Option g or gamma"; shift;;
--) shift ; break ;;
*) echo "Internal error!" ; exit 1 ;;
esac
done
echo Remaining args
for arg in "$#"
do
echo '--> '"\`$arg'"
done
exit 0
Don't do it that way, try this:
if [[ -z $1 ]]; then
echo "Error"
fi
The error in your version is actually the lack of quoting.
Should be:
if [ ! -n "$1" ]; then
echo "Error"
fi
But you don't need the negation, use -z instead.
If you work on Bash, then use double brackets [[ ]] too.
from the man bash page:
-z string
True if the length of string is zero.
-n string
True if the length of string is non-zero.
Also, if you use bash v4 or greater (bash --version) there's -v
-v varname
True if the shell variable varname is set (has been assigned a value).
The trick is "$1", i.e.
root#root:~# cat auto.sh
Usage () {
echo "error"
}
if [ ! -n $1 ];then
Usage
exit 1
fi
root#root:~# bash auto.sh
root#root:~# cat auto2.sh
Usage () {
echo "error"
}
if [ ! -n "$1" ];then
Usage
exit 1
fi
root#root:~# bash auto2.sh
error

How do i compare 2 strings in shell?

I want the user to input something at the command line either -l or -e.
so e.g. $./report.sh -e
I want an if statement to split up whatever decision they make so i have tried...
if [$1=="-e"]; echo "-e"; else; echo "-l"; fi
obviously doesn't work though
Thanks
I use:
if [[ "$1" == "-e" ]]; then
echo "-e"
else
echo "-l";
fi
However, for parsing arguments, getopts might make your life easier:
while getopts "el" OPTION
do
case $OPTION in
e)
echo "-e"
;;
l)
echo "-l"
;;
esac
done
If you want it all on one line (usually it makes it hard to read):
if [ "$1" = "-e" ]; then echo "-e"; else echo "-l"; fi
You need spaces between the square brackets and what goes inside them. Also, just use a single =. You also need a then.
if [ $1 = "-e" ]
then
echo "-e"
else
echo "-l"
fi
The problem specific to -e however is that it has a special meaning in echo, so you are unlikely to get anything back. If you try echo -e you'll see nothing print out, while echo -d and echo -f do what you would expect. Put a space next to it, or enclose it in brackets, or have some other way of making it not exactly -e when sending to echo.
If you just want to print which parameter the user has submitted, you can simply use echo "$1". If you want to fall back to a default value if the user hasn't submitted anything, you can use echo "${1:--l} (:- is the Bash syntax for default values). However, if you want really powerful and flexible argument handling, you could look into getopt:
params=$(getopt --options f:v --longoptions foo:,verbose --name "my_script.sh" -- "$#")
if [ $? -ne 0 ]
then
echo "getopt failed"
exit 1
fi
eval set -- "$params"
while true
do
case $1 in
-f|--foo)
foobar="$2"
shift 2
;;
-v|--verbose)
verbose='--verbose'
shift
;;
--)
while [ -n "$3" ]
do
targets[${#targets[*]}]="$2"
shift
done
source_dir=$(readlink -fn -- "$2")
shift 2
break
;;
*)
echo "Unhandled parameter $1"
exit 1
;;
esac
done
if [ $# -ne 0 ]
then
error "Extraneous parameters." "$help_info" $EX_USAGE
fi

Resources