Prompt user confimation in Bash shell script for GNU Bash 4.3 - linux

I'm trying to create a shell script that sets up my Ubuntu server for a Laravel app. The user is asked to confirm before proceeding with the following code taken from here:
How do I prompt a user for confirmation in bash script?
#!/bin/sh
RED='\033[0;31m'
GREEN='\033[0;32m'
NC='\033[0m'
echo "\n ${GREEN}Enter the folder name for the Laravel application: ${NC}"
read APP_NAME
read -r -p "Are you sure? [y/N] " response
response=${response,,} # tolower
if [[ $response =~ ^(yes|y)$ ]]
then
echo "Installing dependencies..."
else
exit
fi
I'm getting this error:
Bad substitution
on the line
response=${response,,} # tolower

This is a case modification substitution. Here is the description (from the Bash manual on shell parameter expansion):
${parameter^pattern}
${parameter^^pattern}
${parameter,pattern}
${parameter,,pattern}
This expansion modifies the case of alphabetic characters in
parameter. The pattern is expanded to produce a pattern just as in
filename expansion. Each character in the expanded value of parameter
is tested against pattern, and, if it matches the pattern, its case is
converted. The pattern should not attempt to match more than one
character. The ‘^’ operator converts lowercase letters matching
pattern to uppercase; the ‘,’ operator converts matching uppercase
letters to lowercase. The ‘^^’ and ‘,,’ expansions convert each
matched character in the expanded value; the ‘^’ and ‘,’ expansions
match and convert only the first character in the expanded value. If
pattern is omitted, it is treated like a ‘?’, which matches every
character. If parameter is ‘#’ or ‘*’, the case modification 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 case modification operation is applied to each member
of the array in turn, and the expansion is the resultant list.
This works on bash >= 4.0.
Alternatively, you can use
response=$(echo "$response" | tr '[:upper:]' '[:lower:]')

Thanks to David C. Rankins help in the comments section this issue was resolved by changing:
#!/bin/sh
to
#!/bin/bash
and changing
echo "string data"
to
echo -e "string data"

Related

Linux Bash's rule for determing a variable name Greedy or Non-Greedy

If you run the following bash script on Ubuntu 12.04:
t="t"
echo "1st Output this $15"
echo "2nd this $test"
The output is:
1st Output this 5
2nd this
How can the first echo takes $1 as a variable name (non-greedy) interpreting it as ${1}5 while the second echo takes $test as a variable name (greedy) instead of interpreting it as ${t}est?
There are two parts to your question:
$15 would always be interpreted as $1, i.e. the first positional parameter1, concatenated with 5. In order to use the fifteenth positional parameter, you'd need to say ${15}.
$test would be interpreted as variable test. So if you want it to be interpreted as $t concatenated with est, then you need to say ${t}est
1Quoting from info bash:
When a positional parameter consisting of more than a single digit is
expanded, it must be enclosed in braces (see EXPANSION below).
...
EXPANSION
Expansion is performed on the command line after it has been split into
words. There are seven kinds of expansion performed: brace expansion,
tilde expansion, parameter and variable expansion, command substitu‐
tion, arithmetic expansion, word splitting, and pathname expansion.
You may also want to refer to:
What's the difference between ${varname} and $varname in a shell scripts

Use sed/grep to get string from tail to a char in middle

I have some strings (variables) i need to edit in the bash for further analysis
They consist of things like
str="~/folder/item"
How can I use sed or grep to grab just "item" (meaning from tail to char '/')?
Refer to Shell Parameter Expansion. You can say:
$ str="~/folder/item"
$ echo ${str##*/}
item
Quoting from the manual:
${parameter##word}
The word is expanded to produce a pattern just as in filename
expansion (see Filename Expansion). If the pattern matches the
beginning of the expanded 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.
Using grep:
$ grep -Po '.*/\K.*' <<< $str
item
Assuming you are dealing with a text file containing lines like the one shown, then:
sed 's%.*/\([^/]*\)"%\1%' <<< 'str="~/folder/item"'
This yields:
item
If you are dealing with a variable str that contains a string ~/folder/item, then you can use:
basename "$str"
or:
echo "${str##*/}"
basename "${str}" gives the last part of the string after the final /, including any file extensions you may have. If you want to do the opposite and grab the directory, use dirname "${str}"
echo $str|awk -F"/" '{print $NF}'
or
echo "$str" | perl -pe 's/.*\///g'
basename also suitable for this
for example
str="~/folder/item"
name=$(basename $file)
echo $name
the output would be "item"

How to echo "$x_$y" in Bash script?

It is very interesting that if you intend to display 0_1 with Bash using the code
x=0
y=1
echo "$x_$y"
then it will only display
1
I tried echo "$x\_$y" and it doesn't work.
How can I echo the form $x_$y? I'm going to use it on a file name string.
Because variable names are allowed to have underscores in them, the command:
echo "$x_$y"
is trying to echo ${x_} (which is probably empty in your case) followed by ${y}. The reason for this is because parameter expansion is a greedy operation - it will take as many legal characters as possible after the $ to form a variable name.
The relevant part of the bash manpage states:
The $ character introduces parameter expansion, command substitution, or arithmetic expansion.
The parameter name or symbol to be expanded may be enclosed in braces, which are optional but serve to protect the variable to be expanded from characters immediately following it which could be interpreted as part of the name.
When braces are used, the matching ending brace is the first } not escaped by a backslash or within a quoted string, and not within an embedded arithmetic expansion, command substitution, or parameter expansion.
Hence, the solution is to ensure that the _ is not treated as part of the first variable, which can be done with:
echo "${x}_${y}"
I tend to do all my bash variables like this, even standalone ones like:
echo "${x}"
since it's more explicit, and I've been bitten so many times in the past :-)
This way:
$ echo "${x}_${y}"
0_1
wrap it in curly braces:
echo "${x}_${y}"
Just to buck the trend, you can also do this:
echo $x'_'$y
You can have quoted and unquoted parts next to each other with no space between. And since ' isn't a legal character for a variable name, bash will substitute only $x. :)

Whitespace as an argument of a ksh script

Suppose you have the following script:
#!/usr/bin/ksh
for element in $#
do
echo $element
done
And you execute the script using:
./myscript.ksh "my element"
The expected output should be:
my element
but the whitespace is treated as a separator for each argument, obtaining:
my
element
How should I escape the whitespace?
Thanks
You must enclose $#, see Special Parameters, in double quotes:
($#) Expands to the positional parameters, starting from one.
...
When the expansion occurs within double quotes, and word splitting
is performed, each parameter expands to a separate word. That is,
"$#" is equivalent to "$1" "$2" ….
#!/usr/bin/ksh
for element in "$#"
do
echo $element
done
You can get the same output by omitting in "$#" (Looping Constructs, for) altogether:
If ‘in words’ is not present, the for command executes the commands
once for each positional parameter that is set, as if ‘in "$#"’ had
been specified
#!/usr/bin/ksh
for element
do
echo $element
done
You must either quote the $#, or use nothing:
for element in "$#"
or
for element
will both work.

Extract file basename without path and extension in bash [duplicate]

This question already has answers here:
Extract filename and extension in Bash
(38 answers)
Closed 6 years ago.
Given file names like these:
/the/path/foo.txt
bar.txt
I hope to get:
foo
bar
Why this doesn't work?
#!/bin/bash
fullfile=$1
fname=$(basename $fullfile)
fbname=${fname%.*}
echo $fbname
What's the right way to do it?
You don't have to call the external basename command. Instead, you could use the following commands:
$ s=/the/path/foo.txt
$ echo "${s##*/}"
foo.txt
$ s=${s##*/}
$ echo "${s%.txt}"
foo
$ echo "${s%.*}"
foo
Note that this solution should work in all recent (post 2004) POSIX compliant shells, (e.g. bash, dash, ksh, etc.).
Source: Shell Command Language 2.6.2 Parameter Expansion
More on bash String Manipulations: http://tldp.org/LDP/LG/issue18/bash.html
The basename command has two different invocations; in one, you specify just the path, in which case it gives you the last component, while in the other you also give a suffix that it will remove. So, you can simplify your example code by using the second invocation of basename. Also, be careful to correctly quote things:
fbname=$(basename "$1" .txt)
echo "$fbname"
A combination of basename and cut works fine, even in case of double ending like .tar.gz:
fbname=$(basename "$fullfile" | cut -d. -f1)
Would be interesting if this solution needs less arithmetic power than Bash Parameter Expansion.
Here are oneliners:
$(basename "${s%.*}")
$(basename "${s}" ".${s##*.}")
I needed this, the same as asked by bongbang and w4etwetewtwet.
Pure bash, no basename, no variable juggling. Set a string and echo:
p=/the/path/foo.txt
echo "${p//+(*\/|.*)}"
Output:
foo
Note: the bash extglob option must be "on", (Ubuntu sets extglob "on" by default), if it's not, do:
shopt -s extglob
Walking through the ${p//+(*\/|.*)}:
${p -- start with $p.
// substitute every instance of the pattern that follows.
+( match one or more of the pattern list in parenthesis, (i.e. until item #7 below).
1st pattern: *\/ matches anything before a literal "/" char.
pattern separator | which in this instance acts like a logical OR.
2nd pattern: .* matches anything after a literal "." -- that is, in bash the "." is just a period char, and not a regex dot.
) end pattern list.
} end parameter expansion. With a string substitution, there's usually another / there, followed by a replacement string. But since there's no / there, the matched patterns are substituted with nothing; this deletes the matches.
Relevant man bash background:
pattern substitution:
${parameter/pattern/string}
Pattern substitution. The pattern is expanded to produce a pat
tern just as in pathname expansion. Parameter is expanded and
the longest match of pattern against its value is replaced with
string. If pattern begins with /, all matches of pattern are
replaced with string. Normally only the first match is
replaced. If pattern begins with #, it must match at the begin‐
ning of the expanded value of parameter. If pattern begins with
%, it must match at the end of the expanded value of parameter.
If string is null, matches of pattern are deleted and the / fol
lowing pattern may be omitted. If parameter is # or *, the sub
stitution 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 substitution
operation is applied to each member of the array in turn, and
the expansion is the resultant list.
extended pattern matching:
If the extglob shell option is enabled using the shopt builtin, several
extended pattern matching operators are recognized. In the following
description, a pattern-list is a list of one or more patterns separated
by a |. Composite patterns may be formed using one or more of the fol
lowing sub-patterns:
?(pattern-list)
Matches zero or one occurrence of the given patterns
*(pattern-list)
Matches zero or more occurrences of the given patterns
+(pattern-list)
Matches one or more occurrences of the given patterns
#(pattern-list)
Matches one of the given patterns
!(pattern-list)
Matches anything except one of the given patterns
Here is another (more complex) way of getting either the filename or extension, first use the rev command to invert the file path, cut from the first . and then invert the file path again, like this:
filename=`rev <<< "$1" | cut -d"." -f2- | rev`
fileext=`rev <<< "$1" | cut -d"." -f1 | rev`
If you want to play nice with Windows file paths (under Cygwin) you can also try this:
fname=${fullfile##*[/|\\]}
This will account for backslash separators when using BaSH on Windows.
Just an alternative that I came up with to extract an extension, using the posts in this thread with my own small knowledge base that was more familiar to me.
ext="$(rev <<< "$(cut -f "1" -d "." <<< "$(rev <<< "file.docx")")")"
Note: Please advise on my use of quotes; it worked for me but I might be missing something on their proper use (I probably use too many).
Use the basename command. Its manpage is here: http://unixhelp.ed.ac.uk/CGI/man-cgi?basename

Resources