How do we match a suffix in a string in bash? - 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.

Related

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

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.

Meaning of ## in shell

I recently got this script to match a string in command line aguments:
if [[ "$#" == "${##foo}" ]]; then echo "not found" ; else echo "found"; fi
Looks like it does negative search. What is the meaning of ## exactly?
from man bash under Parameter Expansion:
${parameter#word}
${parameter##word}
Remove matching prefix pattern. The word is expanded to produce a pattern just as in pathname expansion. If the pattern
matches the beginning 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.
The $# is expanded to the list of script arguments each enclosed into quotes:
"$1" "$2" "$3"...
thus the "${##foo}" would expand to a list of quoted script arguments with stripped out prefix foo if such prefix exist in any of expanded strings, i.e. the script test:
#!/bin/bash
echo "${##foo}"
invoked as:
./test abc foodef
outputs
abc def

Passing a command as a variable to a function in Unix

Can you pass a command as a variable to another function? If yes, what is the syntax?
My code looks something like.
function1(){
for a in $1
do
echo $a
done
}
function2(){
function1 "ls -lrt folder/Name | grep 'foo' | grep 'bar'"
}
But this doesn't work. I even tried passing it as:
function1 `ls -lrt folder/Name | grep 'foo' | grep 'bar'`
But this passes only the first value of the command (and I understand why this happens).
Does anyone know the syntax to pass a command to function as a variable?
As indicated in comments, you are using $1 but this will just contain the first parameter. Instead, you need to use $#.
From Bash Manual → 3.4.2 Special Parameters:
$#
($#) Expands to the positional parameters, starting from one. When the
expansion occurs within double quotes, each parameter expands to a
separate word. That is, "$#" is equivalent to "$1" "$2" …. If the
double-quoted expansion occurs within a word, the expansion of the
first parameter is joined with the beginning part of the original
word, and the expansion of the last parameter is joined with the last
part of the original word. When there are no positional parameters,
"$#" and $# expand to nothing (i.e., they are removed).
Then you ask
What if the function1 takes multiple parameters? And the command is
the third parameter being passed, then using $# takes in some
unnecessary values. How do I take values from say $3 onwards?
For this, you want to use shift:
$ cat myscript.sh
#!/bin/bash
shift 2
echo "$#"
$ ./myscript.sh a b c d
c d
shift
shift [n]
Shift the positional parameters to the left by n. The positional
parameters from n+1 … $# are renamed to $1 … $#-n. Parameters
represented by the numbers $# to $#-n+1 are unset. n must be a
non-negative number less than or equal to $#. If n is zero or greater
than $#, the positional parameters are not changed. If n is not
supplied, it is assumed to be 1. The return status is zero unless n is
greater than $# or less than zero, non-zero otherwise.

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

Why doesn't [[ ... ]] work when script called with sh, while [ ... ] works always?

Script test.sh:
#!/bin/bash
if [[ $# -eq 0 ]]; then
echo "no arg"
else
echo "have arg"
fi
When I ran it as
./test.sh
it said "no arg", which was expected, but if I run it as
sh ./test.sh
it prints "have arg", but it you print $#, it's zero in both cases.
However, the script below
#!/bin/bash
if [ $# -eq 0 ]; then
echo "no arg"
else
echo "have arg"
fi
prints "no arg" in both cases.
Could somebody explain this? Why does [[ ... ]] interpret $# differently from [ ... ]?
The explanations I've read about [[ ... ]] weren't clear enough about this.
/bin/sh is often a different shell interpreter than bash. On my ubuntu system, it's a symlink to dash. The different shells have different syntax.
[ foo ] and test foo are equivalent in both bash and dash.
[[ foo ]] is a bash expression that is similar to [ ] tests, but with some differences that are noted in man bash.
Dash does not have a [[ command, which results in an error-exitcode, and the execution of the else branch.
[[ expression ]]
Return a status of 0 or 1 depending on the evaluation of the
conditional expression expression. Expressions are composed of
the primaries described below under CONDITIONAL EXPRESSIONS.
Word splitting and pathname expansion are not performed on the
words between the [[ and ]]; tilde expansion, parameter and
variable expansion, arithmetic expansion, command substitution,
process substitution, and quote removal are performed. Condi‐
tional operators such as -f must be unquoted to be recognized as
primaries.
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. If the shell
option nocasematch is enabled, the match is performed without
regard to the case of alphabetic characters. The return value
is 0 if the string matches (==) or does not match (!=) the pat‐
tern, and 1 otherwise. Any part of the pattern may be quoted to
force it to be matched as a string.
An additional binary operator, =~, is available, with the same
precedence as == and !=. When it is used, the string to the
right of the operator is considered an extended regular expres‐
sion and matched accordingly (as in regex(3)). The return value
is 0 if the string matches the pattern, and 1 otherwise. If the
regular expression is syntactically incorrect, the conditional
expression's return value is 2. If the shell option nocasematch
is enabled, the match is performed without regard to the case of
alphabetic characters. Any part of the pattern may be quoted to
force it to be matched as a string. Substrings matched by
parenthesized subexpressions within the regular expression are
saved in the array variable BASH_REMATCH. The element of
BASH_REMATCH with index 0 is the portion of the string matching
the entire regular expression. The element of BASH_REMATCH with
index n is the portion of the string matching the nth parenthe‐
sized subexpression.
In my case (ubuntu 9.04) I also see the following error line ABOVE the "have arg" line:
./test.sh: 6: [[: not found
And indeed that's as it should be; /bin/sh is a symbolic link to 'dash' not 'bash'.

Resources