Shell Scripting question | ${-#*i} meaning in profile - shell

I was looking into a shell file and I have found this part:
for i in /etc/profile.d/*.sh ; do
if [ -r "$i" ]; then
if [ "${-#*i}" != "$-" ]; then
. "$i"
else
. "$i" >/dev/null 2>&1
fi
fi
done
and I was wondering what does ${-#*i} mean in this context ?

In a POSIX shell, - is a special shell variable. From the man page:
- (Hyphen.) Expands to the current option flags (the single-letter option
names concatenated into a string) as specified on invocation,
by the set builtin command, or implicitly by the shell.
The # is used to return a variable's value with part of it removed:
${parameter#word} Remove Smallest Prefix Pattern. The word is expanded
to produce a pattern. The parameter expansion then
results in parameter, with the smallest portion of the
prefix matched by the pattern deleted.

Related

If else condition match statements not working inside shell of jenkinsfile

I have below statement in one of the steps in Jenkinsfile
steps {
sh '''
file=/sql/common/file1.sql
echo $file
if ["$file" = *"/common"* ]; then
echo "changes found in common directory "
fi
'''
}
For some reason shell is not working properly inside jenkinsfile. how do we
compare strings in shell in Jenkinsfile? do we have any specific syntax for these? Jenkins give error if I use == operator to compare the strings.
My assumption was shell should work same way in Jenkinsfile once we declare it inside sh '''. Is that not the case?
["$file"
is invalid. There must be a space between [ and the argument. [ is a command.
if [ "$file" = *"/common"* ];
doesn't mean what you think it does. *"/common"* undergoes filename expansion, so it is replaced by a list of files that match the pattern. Because there are most probably many files that match the filename expansion, [ program exits with some kind of a a syntax error.
If you want to match a string against a pattern in POSIX shell, either use grep with a regular expression:
if printf "%s\n" "$file" | grep -q '.*/common.*'; then
or use case with glob:
if case "$file" in *"/common"*) true; ;; *) false; ;; esac; then

Bash: How to set a variable from argument, and with a default value

It is pretty clear that with shell scripting this sort of thing can be accomplished in a huge number of ways (more than most programming languages) because of all the different variable expansion methods and programs like test and [ and [[, etc.
Right now I'm just looking for
DIR=$1 or .
Meaning, my DIR variable should contain either what is specified in the first arg or the current directory.
What is the difference between this and DIR=${1-.}?
I find the hyphen syntax confusing, and seek more readable syntax.
Why can't I do this?
DIR="$1" || '.'
I'm guessing this means "if $1 is empty, the assignment still works (DIR becomes empty), so the invalid command '.' never gets executed."
I see several questions here.
“Can I write something that actually reflects this logic”
Yes. There are a few ways you can do it. Here's one:
if [[ "$1" != "" ]]; then
DIR="$1"
else
DIR=.
fi
“What is the difference between this and DIR=${1-.}?”
The syntax ${1-.} expands to . if $1 is unset, but expands like $1 if $1 is set—even if $1 is set to the empty string.
The syntax ${1:-.} expands to . if $1 is unset or is set to the empty string. It expands like $1 only if $1 is set to something other than the empty string.
“Why can't I do this? DIR="$1" || '.'”
Because this is bash, not perl or ruby or some other language. (Pardon my snideness.)
In bash, || separates entire commands (technically it separates pipelines). It doesn't separate expressions.
So DIR="$1" || '.' means “execute DIR="$1", and if that exits with a non-zero exit code, execute '.'”.
How about this:
DIR=.
if [ $# -gt 0 ]; then
DIR=$1
fi
$# is the number of arguments given to the script, and -gt means "greater than", so you basically set DIR to the default value, and if the user has specified an argument, then you set DIR to that instead.
I use a simple helper function to make such assignments look cleaner. The function below accepts any number of arguments, but returns the first one that's not the empty string.
default_value() {
# Return the first non-empty argument
while [[ "$1" == "" ]] && [[ "$#" -gt "0" ]]; do
shift
done
echo $1
}
x=$(default_value "$1" 0)

How do we match a suffix in a string in bash?

I want to check if an input parameter ends with ".c"? How do I check that? Here is what I got so far (Thanks for your help):
#!/bin/bash
for i in $#
do
if [$i ends with ".c"]
then
echo "YES"
fi
done
A classical case for case!
case $i in *.c) echo Yes;; esac
Yes, the syntax is arcane, but you get used to it quickly. Unlike various Bash and POSIX extensions, this is portable all the way back to the original Bourne shell.
$ [[ foo.c = *.c ]] ; echo $?
0
$ [[ foo.h = *.c ]] ; echo $?
1
for i in $#; do
if [ -z ${i##*.c} ]; then
echo "YES: $i"
fi
done
$ ./test.sh .c .c-and-more before.c-and-after foo.h foo.c barc foo.C
YES: .c
YES: foo.c
$
Explanation (thanks to jpaugh):
Iterate over command line arguments: for i in $#; do
Main trick is here: if [ -z ${i##*.c} ]; then. Here we check if length of string ${i##*.c} is zero. ${i##*.c} means: take $i value and remove substring by template "*.c". If result is empty string, then we have ".c" suffix.
Here if some additional info from man bash, section Parameter Expasion
${parameter#word}
${parameter##word}
Remove matching prefix pattern. The word is expanded to produce a pat‐
tern just as in pathname expansion. If the pattern matches the begin‐
ning of the value of parameter, then the result of the expansion is the
expanded value of parameter with the shortest matching pattern (the
``#'' case) or the longest matching pattern (the ``##'' case) deleted.
If parameter is # or *, the pattern removal operation is applied to
each positional parameter in turn, and the expansion is the resultant
list. If parameter is an array variable subscripted with # or *, the
pattern removal operation is applied to each member of the array in
turn, and the expansion is the resultant list.

Bash - if and for statements

I am little unfamiliar with the 'if...then...fi' and the 'for' statements syntax.
Could anyone explain what the "$2/$fn" and "/etc/*release" in the code snippets below mean?...specifically on the use of the forward slash....and the asterisk...
if [ -f "$filename" ]; then
if [ ! -f "$2/$fn" ]; then
echo "$fn is missing from $2"
missing=$((missing + 1))
fi
fi
and
function system_info
{
if ls /etc/*release 1>/dev/null 2>&1; then
echo "<h2>System release info</h2>"
echo "<pre>"
for i in /etc/*release; do
# Since we can't be sure of the
# length of the file, only
# display the first line.
head -n 1 $i
done
uname -orp
echo "</pre>"
fi
} # end of system_info
...thx for the help...
/etc/*release : here the * will match any number of any characters, so any thing /etc/0release , /etc/asdfasdfr_release etc will be matched. Simply stated, it defined all the files in the /etc/ directory which ends with the string release.
The $2 is the 2nd commandline argument to the shell script, and $fn is some other shell variable. The "$2/$fn" after the variable substitutions will make a string, and the [ -f "$2/$fn" ] will test if the string formed after the substitution forms a path to a regular file which is specified by the -f switch. If it is a regular file then the body of if is executed.
In the for loop the loop will loop for all the files ending with the string release in the directory /etc (the path). At each iteration i will contain the next such file name, and for each iteration the first 1 line of the file is displayed with the head command by getting the file name from variable i within the body.
It is better to check the manual man bash and for if condition check man test . Here is a good resource: http://tldp.org/LDP/Bash-Beginners-Guide/html/
The forward slash is the path separator, and the * is a file glob character. $2/$fn is a path where $2 specifies the directory and $fn is the filename. /etc/*release expands to the space separated list of all the files in /etc whose name ends in "release"
Dollar sign marks variable. The "-f" operator means "file exsists".
So,
[ -f "$filename" ]
checks if there is file named the same as value contained in $filename variable.
Simmilar, if we assume that $2 = "some_folder", and $fn = "some_file", expression
[ ! -f "$2/$fn" ]
returns true if file some_folder/some_file doesn't exsist.
Now, about asterisk - it marks "zero or more of any character(s)". So, expression:
for i in /etc/*release; do
will iterate trough all folders named by that pattern, for example:
/etc/release, /etc/666release, /etc/wtf_release...
I hope this helps.

What is the meaning of ${0%/*} in a bash script?

I am trying to understand a test script, which includes the following segment:
SCRIPT_PATH=${0%/*}
if [ "$0" != "$SCRIPT_PATH" ] && [ "$SCRIPT_PATH" != "" ]; then
cd $SCRIPT_PATH
fi
What does the ${0%/*} stand for? Thanks
It is called Parameter Expansion. Take a look at this page and the rest of the site.
What ${0%/*} does is, it expands the value contained within the argument 0 (which is the path that called the script) after removing the string /* suffix from the end of it.
So, $0 is the same as ${0} which is like any other argument, eg. $1 which you can write as ${1}. As I said $0 is special, as it's not a real argument, it's always there and represents name of script. Parameter Expansion works within the { } -- curly braces, and % is one type of Parameter Expansion.
%/* matches the last occurrence of / and removes anything (* means anything) after that character. Take a look at this simple example:
$ var="foo/bar/baz"
$ echo "$var"
foo/bar/baz
$ echo "${var}"
foo/bar/baz
$ echo "${var%/*}"
foo/bar

Resources