Checking the umask in shell script - bash

How do I check if the umask prevents the group bits from being set? My attempt:
#!/bin/sh
out=$(umask)
echo "$out"
if (($out & 070) != 0); then
echo "$out"
echo "Incorrect umask" > /dev/tty
exit 1
fi
Output:
./test.sh: line 6: syntax error near unexpected token `!='
./test.sh: line 6: `if (($out & 070) != 0); then'
I'm ok with switching to bash if it makes things easier.

You need to use double parentheses to get an arithmetic evaluation. See https://www.gnu.org/software/bash/manual/bashref.html#Conditional-Constructs
m=$(umask)
if (( ($m & 070) != 0 )); then
echo error
fi
Or you could treat the umask as a string and use glob-pattern matching:
if [[ $m == *0? ]]; then
echo OK
else
echo err
fi
bash has lots of unique syntax: it's not C/perl-like at all. Read (or at least refer to) the manual and read lots of bash questions here. Keep asking questions.

Related

Bash script with multiline variable

Here is my code
vmname="$1"
EXCEPTLIST="desktop-01|desktop-02|desktop-03|desktop-04"
if [[ $vmname != #(${EXCEPTLIST}) ]]; then
echo "${vmname}"
else
echo "Its in the exceptlist"
fi
The above code works perfectly but my question is , the EXCEPTLIST can be a long line, say 100 server names. In that case its hard to put all that names in one line. In that situation is there any way to make the variable EXCEPTLIST to be a multiline variable ? something like as follows:
EXCEPTLIST="desktop-01|desktop-02|desktop-03| \n
desktop-04|desktop-05|desktop-06| \n
desktop-07|desktop-08"
I am not sure but was thinking of possibilities.
Apparently I would like to know the terminology of using #(${})- Is this called variable expansion or what ? Does anyone know the documentation/explain to me about how this works in bash. ?
One can declare an array if the data/string is long/large. Use IFS and printf for the format string, something like:
#!/usr/bin/env bash
exceptlist=(
desktop-01
desktop-02
desktop-03
desktop-04
desktop-05
desktop-06
)
pattern=$(IFS='|'; printf '#(%s)' "${exceptlist[*]}")
[[ "$vmname" != $pattern ]] && echo good
In that situation is there any way to make the variable EXCEPTLIST to be a multiline variable ?
With your given input/data an array is also a best option, something like:
exceptlist=(
'desktop-01|desktop-02|desktop-03'
'desktop-04|desktop-05|desktop-06'
'desktop-07|desktop-08'
)
Check what is the value of $pattern variable one way is:
declare -p pattern
Output:
declare -- pattern="#(desktop-01|desktop-02|desktop-03|desktop-04|desktop-05|desktop-06)"
Need to test/check if $vmname is an empty string too, since it will always be true.
On a side note, don't use all upper case variables for purely internal purposes.
The $(...) is called Command Substitution.
See LESS=+'/\ *Command Substitution' man bash
In addition to what was mentioned in the comments about pattern matching
See LESS=+/'(pattern-list)' man bash
See LESS=+/' *\[\[ expression' man bash
s there any way to make the variable EXCEPTLIST to be a multiline variable ?
I see no reason to use matching. Use a bash array and just compare.
exceptlist=(
desktop-01
desktop-02
desktop-03
desktop-04
desktop-05
desktop-06
)
is_in_list() {
local i
for i in "${#:2}"; do
if [[ "$1" = "$i" ]]; then
return 0
fi
done
return 1
}
if is_in_list "$vmname" "${EXCEPTLIST[#]}"; then
echo "is in exception list ${vmname}"
fi
#(${})- Is this called variable expansion or what ? Does anyone know the documentation/explain to me about how this works in bash. ?
${var} is a variable expansion.
#(...) are just characters # ( ).
From man bash in Compund commands:
[[ expression ]]
When the == and != operators are used, the string to the right of the operator is considered a pattern and matched according to the rules
described below under Pattern Matching, as if the extglob shell option were enabled. ...
From Pattern Matching in man bash:
#(pattern-list)
Matches one of the given patterns
[[ command receives the #(a|b|c) string and then matches the arguments.
There is absolutely no need to use Bash specific regex or arrays and loop for a match, if using grep for raw string on word boundary.
The exception list can be multi-line, it will work as well:
#!/usr/bin/sh
exceptlist='
desktop-01|desktop-02|desktop-03|
deskop-04|desktop-05|desktop-06|
desktop-07|deskop-08'
if printf %s "$exceptlist" | grep -qwF "$1"; then
printf '%s is in the exceptlist\n' "$1"
fi
I wouldn't bother with multiple lines of text. This is would be just fine:
EXCEPTLIST='desktop-01|desktop-02|desktop-03|'
EXCEPTLIST+='desktop-04|desktop-05|desktop-06|'
EXCEPTLIST+='desktop-07|desktop-08'
The #(...) construct is called extended globbing pattern and what it does is an extension of what you probably already know -- wildcards:
VAR='foobar'
if [[ "$VAR" == fo?b* ]]; then
echo "Yes!"
else
echo "No!"
fi
A quick walkthrough on extended globbing examples: https://www.linuxjournal.com/content/bash-extended-globbing
#!/bin/bash
set +o posix
shopt -s extglob
vmname=$1
EXCEPTLIST=(
desktop-01 desktop-02 desktop-03
...
)
if IFS='|' eval '[[ ${vmname} == #(${EXCEPTLIST[*]}) ]]'; then
...
Here's one way to load a multiline string into a variable:
fn() {
cat <<EOF
desktop-01|desktop-02|desktop-03|
desktop-04|desktop-05|desktop-06|
desktop-07|desktop-08
EOF
}
exceptlist="$(fn)"
echo $exceptlist
As to solving your specific problem, I can think of a variety of approaches.
Solution 1, since all the desktop has the same desktop-0 prefix and only differ in the last letter, we can make use of {,} or {..} expansion as follows:
vmname="$1"
found=0
for d in desktop-{01..08}
do
if [[ "$vmname" == $d ]]; then
echo "It's in the exceptlist"
found=1
break
fi
done
if (( !found )); then
echo "Not found"
fi
Solution 2, sometimes, it is good to provide a list in a maintainable clear text list. We can use a while loop and iterate through the list
vmname="$1"
found=0
while IFS= read -r d
do
if [[ "$vmname" == $d ]]; then
echo "It's in the exceptlist"
found=1
break
fi
done <<EOF
desktop-01
desktop-02
desktop-03
desktop-04
desktop-05
desktop-06
desktop-07
desktop-08
EOF
if (( !found )); then
echo "Not found"
fi
Solution 3, we can desktop the servers using regular expressions:
vmname="$1"
if [[ "$vmname" =~ ^desktop-0[1-8]$ ]]; then
echo "It's in the exceptlist"
else
echo "Not found"
fi
Solution 4, we populate an array, then iterate through an array:
vmname="$1"
exceptlist=()
exceptlist+=(desktop-01 desktop-02 desktop-03 deskop-04)
exceptlist+=(desktop-05 desktop-06 desktop-07 deskop-08)
found=0
for d in ${exceptlist[#]}
do
if [[ "$vmname" == "$d" ]]; then
echo "It's in the exceptlist"
found=1
break;
fi
done
if (( !found )); then
echo "Not found"
fi

Filter out invalid PIDs portably

I wrote a script to fetch the Top Level Session PID, that is, the session starter, who may be a shell like bash, dash, ksh, or even systemd. The script may get a PID as a initial parameter however I need to filter it to check it is a valid integer and not something like 34fg45, -5467 and I don't want it starting with a zero like 05467.
This is a snippet of the script.
if [ "$1" != "" ]; then
if [[ "$1" == [1-9]*([0-9]) ]]; then <- Check for Integer; error here in non bash shell
if ps -p $1 -o "pid=" >/dev/null 2>&1; then
pid=$1
else
echo "PID $1, no such process." >&2
exit 1
fi
else
echo "Invalid pid." >&2
exit 1
fi
else
pid=$$
fi
The code runs in bash, but fails to run on dash with a syntax error:
./tspid: 16: ./tspid: Syntax error: "(" unexpected (expecting "then")
It is my understanding that
if [[ "$1" =~ ^[0-9][1-9]*$ ]]; using =~ does regular expression matching, and
if [[ "$1" == [1-9]*([0-9]) ]]; using == does pattern matching
Is that right?
How to transform the above expressions to run in both, non-bash as well in bash shells ?
Use case conditional construct. Every POSIX shell has it and unlike double brackets it doesn't look horrible.
# make sure 0-9 is literally 0 to 9
LC_COLLATE=C
# assume set -u is not in effect or $1 is set
case $1 in
('')
# handle empty argument
;;
(0*|*[!0-9]*)
# handle invalid PID (0, 042, 42a, etc.)
;;
(*)
# handle valid PID
;;
esac
# restore LC_COLLATE if necessary

Can bash echo command return non-zero exit code?

Like the title says, is there any case where echo will exit non-zero in bash/sh?
code ex.
until monitor_thing_happens; do
test $retry_counter -eq 0 && echo "thing didn't happen" && exit 1
let "retry_counter--"
echo "tries remaining: ${retry_counter}"
sleep 5
done
In the above example, if echo exits non-zero, the && logic breaks, we never exit 1, and we loop forever. Any danger / edge case where echo can exit non-zero?
Yes, echo has a non-zero return status if there's a write error.
Quoting the bash manual:
'echo'
echo [-neE] [ARG ...]
Output the ARGs, separated by spaces, terminated with a newline.
The return status is 0 unless a write error occurs.
A demonstration:
$ cat foo.bash
#!/bin/bash
echo hello
echo "The echo command returned a status of $?" > /dev/tty
$ ./foo.bash > /dev/full
./foo.bash: line 3: echo: write error: No space left on device
The echo command returned a status of 1
$
/dev/full is a device, similar to /dev/zero except that any attempt to write to it will fail with an ENOSPC error.
Nope, no risk. From man bash:
echo [-neE] [arg ...]
Output the args, separated by spaces, followed by a newline.
The return status is always 0. If -n is specified, the trailing
newline is suppressed. If the -e option is given, interpretation of
the following backslash-escaped characters is enabled. The -E option
disables the interpretation of these escape characters, even on systems
where they are interpreted by default. The xpg_echo shell option may
be used to dynamically determine whether or not echo expands these
escape characters by default. echo does not interpret -- to mean the
end of options. echo interprets the following escape sequences:
Emphasis on "The return status is always 0".
From a code quality standpoint, I would recommend not using test unless you're forced to for shell compatibility reasons. In general, use [[, but for arithmetic expressions you can also use ((:
# The generic way
[[ $retry_counter -eq 0 ]] && echo "Thing didn't happen" && exit 1
# The arithmetic way
(( retry_counter == 0 )) && echo "Thing didn't happen" && exit 1
From help man (bash):
Exit Status:
Returns success unless a write error occurs.
UPDATED
So if you echo to a stream that suddenly fails, you will get another exit code.
Different comments show risks.
You can try
retry_counter=5
while [ retry_counter -gt 0 ]; do
monitor_thing_happens && break
(( retry_counter-- ))
echo "tries remaining: ${retry_counter}"
sleep 5
done
Not without risk ! When the function monitor_things_happen resets the same variable retry_counter the loop wil run a long time.

BASH: Pattern match not working

I am using BASH 3.2. I can execute the following from the command line:
$ build_number=23332
$ if [[ $build_number != +([0-9]) ]]
> then
> echo "Bad Build Number ($build_number). No CPU time for you!"
> else
> echo "Build Number ($build_number) is numeric"
> fi
Build Number (2332) is numeric"
If I change build_number to23332a`, this returns:
Bad Build Number (23332a). No CPU time for you!
Now, I'll try to put this into my shell script:
#! /bin/bash
...
#
# Set options
#
while getopts :hu:j:b:p: option
do
case $option in
p) promotion_name="$OPTARG";;
u) jenkins_url="$OPTARG";;
j) job_name="$OPTARG";;
b) build_number="$OPTARG"
if [[ $build_number != +([0-9]) ]]
then
error "Build Number must be numeric"
fi
;;
h) printf "\n$USAGE\n"
exit 0;;
*) error "Invalid Argument";;
esac
done
shift $(( $OPTIND - 1 ))
When I try to execute my program, I get the following error:
$ ./promotion.sh -b 238d -jasdad
./promotion.sh: line 55: syntax error in conditional expression: unexpected token `('
./promotion.sh: line 55: syntax error near `+(['
./promotion.sh: line 55: ` if [[ $build_number != +([0-9]) ]]'
So, what am I doing wrong?
You need to enable extended globbing:
shopt -s extglob
well the most obvious thing is that the plus checks if the pre-ceeding character matches the pattern , here you have no pre-ceeding character put the plus after it

Bash script command result inside other variable to define prompt

I would like to define a prompt which will indicate with colors whether the command executed properly and whether the command was found. As for now I have something like this but I does not work properly.
PS1="\`COMMAND_RESULT=\$\?;
if [ $COMMAND_RESULT -eq 127 ]; then echo \[\e[33m\] ---=== Command not found ===--- ;
elif [ $COMMAND_RESULT -ne 0 ]; then echo \[\e[33m\]---=== \[\e[31m\]Oh noes, bad command \[\e[33m\]==---;
fi\`
\n\[\e[0;37m\][\[\e[1;31m\]\#\[\e[0;37m\]]
\[\e[0;32m\]\u\[\033[1;33m\]#\[\033[0;32m\]\h
As for now I get this error on bash start :
-bash: [: -eq: unary operator expected
-bash: [: -ne: unary operator expected
Don't pollute your PS1 with functions. You should use the special PROMPT_COMMAND variable to do this. The value of PROMPT_COMMAND is executed as a command prior to issuing each primary prompt.
Here is an example:
_check_command(){
local COMMAND_RESULT=$?
if [ $COMMAND_RESULT -eq 127 ]
then
echo -e "\e[1;33m---=== Command not found ===---\e[m"
elif [ $COMMAND_RESULT -ne 0 ]
then
echo -e "\e[1;31m---=== Oh noes, bad command ===---\e[m"
fi
}
PROMPT_COMMAND='_check_command'
PS1="\[\e[0;37m\][\[\e[1;31m\]\#\[\e[0;37m\]] \[\e[0;32m\]\u\[\033[1;33m\]#\[\033[0;32m\]\h "
There are many bash prompts you can find online to guide you. Here is one good example.
You probably should not escape $? as \$\?. Looks like it gets interpreted literally.
Also you can check out the Arch Wiki article that shows how to implement something similar to what you want. Look at this line:
PS1="$(if [[ ${EUID} == 0 ]]; then echo '\[\033[01;31m\]\h'; else echo '\[\033[01;32m\]\u#\h'; fi)\[\033[01;34m\] \w \$([[ \$? != 0 ]] && echo \"\[\033[01;31m\]:(\[\033[01;34m\] \")\$\[\033[00m\] "
especially this part:
([[ \$? != 0 ]] && echo \"\[\033[01;31m\]:(\[\033[01;34m\] \")

Resources