bash truncate string Works on commandline but not in script - string

I am trying to cut off all occurrences of a character from the end of a string.
The test script I came up with is:
#!/bin/bash
Input="/tmp/blah/bloh/////////////"
Desired="/tmp/blah/bloh"
cut='/'
result=${Input%%+(${cut})}
echo " Input: ${Input}"
echo "Expected result: ${Desired}"
echo " Result: ${result}"
echo "---------------------------------------"
echo -n " Outcome: "
[ "${Desired}" = "${result}" ] && echo "Success!" || echo "Fail!"
Running this script via bash /tmp/test.sh gives the following output:
Input: /tmp/blah/bloh/////////////
Expected result: /tmp/blah/bloh
Result: /tmp/blah/bloh/////////////
---------------------------------------
Outcome: Fail!
However, if I copy and paste the entire thing in my console I get the expected result of /tmp/blah/blah
What is going on here?

+(${cut}) is an extended pattern, which bash does not recognize by default. You need to enable the extglob option first.
$ shopt -s extglob
$ Input="/tmp/blah/bloh/////////////"
$ cut='/'
$ echo "${Input%%+(${cut})}"
/tmp/blah/bloh
You probably have extglob enabled in your interactive shell via your .bashrc or .bash_profile configuration file, but neither file is used for the non-interactive shell started by your script.

Related

How to compare dates in Linux

My so.txt contains below line:
PRE 2022-03-16/
Date is = 2022-03-16
awsdate= cut -c 5-14 so.txt
sysdates=`date +%Y-%m-%d`
echo $sysdates
if [[ "$awsdate" == "$sysdates" ]] ;
then
echo "dates are matching"
else
echo "dates are not matching"
fi
Output:
2022-03-16
2022-03-16
dates are not matching
With above script I am trying to compare dates in Linux. Although the dates are same I am still getting dates are not matching. Why is this?
Probably a quoting error. Use quotes and command substitution properly. This works:
so.txt:
PRE 2022-03-16/
test.sh:
Note: shellcheck test.sh comes up clean now.
#!/bin/bash
awsdate="$(cut -c 5-14 so.txt)"
sysdates="$(date +%Y-%m-%d)"
echo "$sysdates"
if [[ "$awsdate" == "$sysdates" ]]; then
echo "dates are matching"
else
echo "dates are not matching"
fi
Cmd and output:
$ ./test.sh
2022-03-16
dates are matching
Run shellcheck on your original script and see what it says for errors.
Here is what I see:
Bad test.sh:
#!/bin/bash
awsdate= cut -c 5-14 so.txt
sysdates=`date +%Y-%m-%d`
echo $sysdates
if [[ "$awsdate" == "$sysdates" ]] ;
then
echo "dates are matching"
else
echo "dates are not matching"
fi
Run shellcheck on it. I am using shellcheck --version of version: 0.8.0. These are all of your errors and problems:
$ shellcheck test.sh
In test.sh line 1:
awsdate= cut -c 5-14 so.txt
^-- SC2148 (error): Tips depend on target shell and yours is unknown. Add a shebang or a 'shell' directive.
^-- SC1007 (warning): Remove space after = if trying to assign a value (for empty string, use var='' ... ).
In test.sh line 2:
sysdates=`date +%Y-%m-%d`
^--------------^ SC2006 (style): Use $(...) notation instead of legacy backticks `...`.
Did you mean:
sysdates=$(date +%Y-%m-%d)
In test.sh line 3:
echo $sysdates
^-------^ SC2086 (info): Double quote to prevent globbing and word splitting.
Did you mean:
echo "$sysdates"
In test.sh line 4:
if [[ "$awsdate" == "$sysdates" ]] ;
^------^ SC2154 (warning): awsdate is referenced but not assigned.
For more information:
https://www.shellcheck.net/wiki/SC2148 -- Tips depend on target shell and y...
https://www.shellcheck.net/wiki/SC1007 -- Remove space after = if trying to...
https://www.shellcheck.net/wiki/SC2154 -- awsdate is referenced but not ass...
References:
my notes on shellcheck: https://github.com/ElectricRCAircraftGuy/eRCaGuy_hello_world/tree/master/bash#shellcheck

while running this programe i occured one error as ambigous redirect

This is my following bash script
cat >> $file_name
And I receive this kind of error:
./l7.sh: line 12: $file_name: ambiguous redirect
Here are the full code
https://github.com/vats147/public/blob/main/l7.sh
And Why I am getting this error? even my syntax is correct.
Into the parameter file_name you must assign $1, which will pass to the current file as an input parameter.
#! /bin/bash
echo -e " Enter file name : \c"
read file_name=$1
if [ -f $file_name ]
then
if [ -w $file_name ]
then
echo " type some text data. to quit press enter "
#cat > $file_name(single angular bracket use for overwritten)
#cat >> $file_name(two angular bracket use for appending a text)
cat >> $file_name
else
echo " file not have write permission"
fi
else
echo "file not exist"
fi
These are positional arguments of the script.
Executing ./script.sh Hello World will make
$0 = ./script.sh
$1 = Hello
$2 = World
Note
If you execute ./script.sh, $0 will give output ./script.sh but if you execute it with bash script.sh it will give output script.sh.

Bash: highlight command before execution (set -x)

I have a bash script which executes about 20 commands and for debugging purposes I find myself scrolling through the output a lot.
Unfortunately bash doesn't tell me which part of the output is part of what command.
When I use "set -x" in the script it at least prints some information on what it just executed, but I don't really like the output it generates.
For instance, if I have this script:
#!/bin/bash
set -x
echo "foo"
if [ "$ASD" == "QWE" ] ; then
echo "bar"
fi
I would like the output to be something like this:
echo "foo"
foo
echo "bar"
bar
Or maybe:
echo "foo"
foo
if [ "value_of_ASD" == "QWE" ] ; then
echo "bar"
bar
fi
Instead of printing the commands in bold, highlighting with a color would also be okay. But I don't just want to have "+" characters in front of the commands and I also don't like the if statements showing up like '[' value_of_ASD == QWE ']'.
How can I accomplish that with bash?
At the moment the output looks like this btw:
+ echo foo
foo
+ '[' value_of_ASD == QWE ']'
+ echo bar
bar
Edit:
One idea I had was to write a script that I would source in the very beginning of the main script and then let the sourced script parse the main one. Something like this:
source_me.sh
#!/bin/bash
SCRIPT_PATH="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )/$(basename $0)"
FORMAT_SET_BOLD='\033[0;1m'
FORMAT_RESET='\033[0m'
cat $SCRIPT_PATH | tail -n "+$START_LINE" | while read line; do
printf "${FORMAT_SET_BOLD}${line}${FORMAT_RESET}\n"
eval "${line}"
done
exit 0;
main.sh
#!/bin/bash
START_LINE=$((LINENO+1)) source ./source_me.sh
echo "Foo"
echo "Bar"
echo "+Hello"
The output in that case is:
echo "Foo"
Foo
echo "Bar"
Bar
echo "+Hello"
+Hello
But this method will fail if I use more complex code that goes over multiple lines (if statements, loops etc):
#!/bin/bash
START_LINE=$((LINENO+1)) source ./source_me.sh
echo "Foo"
if [ "$FOOBAR" == "" ] ; then
echo "Bar"
fi
echo "+Hello"
In this case I get:
echo "Foo"
Foo
if [ "$FOOBAR" == "" ] ; then
./source_me.sh: eval: line 9: syntax error: unexpected end of file
echo "Bar"
Bar
fi
./source_me.sh: eval: line 8: syntax error near unexpected token ´fi'
./source_me.sh: eval: line 8: ´fi'
echo "+Hello"
+Hello
I would like to extend rubo77's answer with a few examples that I think deserve a separate answer:
Plain text prefix
So the basic example is to set PS4 to some plain text, e.g.:
PS4="# "; set -x
Which will result in:
Color & extra line text prefix
But because you can use special characters and ANSI escape codes you can for example add a new line before each new command and print the prefix in a color, e.g.:
PS4="\n\033[1;33m>>>\033[0m "; set -x
Result:
Dynamic color prefix
Finally you can make the command prefix call other programs with each use, which you can use to add a timestamp, e.g.:
# yes, there have to be single quotes below, not double!
PS4='\033[1;34m$(date +%H:%M:%S)\033[0m '; set -x
Result:
You can change the + to a string of your desire by setting PS4, e.g.:
PS4="# "; set -x
Note that set -x has no innate knowledge of your terminal, which would be required to send the correct characters to create bold or coloured text.
If you can capture all output through a single file handle, you can probably do this with a pipe:
$ /path/to/your/script 2>&1 | sed "/^+ /s/.*/$(tput bold)&$(tput sgr0)/"
You might want to man tput for more information about the tool we're using to get bold type.
The viability of this depends on many factors I don't know about your environment. YMMV, may contain nuts.
Note:
You might think it was better to capture stderr separately with a line like this:
$ /path/to/your/script 2> >(sed "/^+ /s/.*/$(tput bold)&$(tput sgr0)/")
Alas, this doesn't behave consistently, because stderr passing through the pipe may not be submitted to the terminal before subsequent stdout from the script itself. Note also that this solution, because it uses process substitution, is a bashism and is not portable to POSIX.
What you are looking for is set -v.
-v prints out the line the interpreter just read.
-x prints out the post-parsing results of the line that the interpreter just read.
file x:
set -vx
foo=1
echo $foo
set +vx
Executing:
$: . x
foo=1
++ foo=1
echo $foo
++ echo 1
1
set +vx
++ set +vx

"printf -v" inside function not working with redirected output

With bash 4.1.2 and 4.3.48, the following script gives the expected output:
#!/bin/bash
returnSimple() {
local __resultvar=$1
printf -v "$__resultvar" '%s' "ERROR"
echo "Hello World"
}
returnSimple theResult
echo ${theResult}
echo Done.
Output as expected:
$ ./returnSimple
Hello World
ERROR
Done.
However, when stdout from the function is piped to another process, the assignment of the __resultvar variable does not work anymore:
#!/bin/bash
returnSimple() {
local __resultvar=$1
printf -v "$__resultvar" '%s' "ERROR"
echo "Hello World"
}
returnSimple theResult | cat
echo ${theResult}
echo Done.
Unexpected Output:
$ ./returnSimple
Hello World
Done.
Why does printf -v not work in the second case? Should printf -v not write the value into the result variable independent of whether the output of the function is piped to another process?
See man bash, section on Pipelines:
Each command in a pipeline is executed as a separate process (i.e., in a subshell).
That's why when you write cmd | cat, cmd receives a copy of variable that it can't modify.
A simple demo:
$ test() ((a++))
$ echo $a
$ test
$ echo $a
1
$ test | cat
$ echo $a
1
Interestingly enough, the same also happens when using eval $__resultvar="'ERROR'" instead of the printf -v statement. Thus, this is not a printf related issue.
Instead, adding a echo $BASH_SUBSHELL to both the main script and the function shows that the shell spawns a sub shell in the second case - since it needs to pipe the output from the function to another process. Hence the function runs in a sub shell:
#!/bin/bash
returnSimple() {
local __resultvar=$1
echo "Sub shell level: $BASH_SUBSHELL"
printf -v "$__resultvar" '%s' "ERROR"
}
echo "Sub shell level: $BASH_SUBSHELL"
returnSimple theResult | cat
echo ${theResult}
echo Done.
Output:
% ./returnSimple.sh
Sub shell level: 0
Sub shell level: 1
Done.
This is the reason why any variable assignments from within the function are not passed back to the calling script.

Execute a input in form of mathematical expression in bash

If a user gives the following input in bash. How can I evaluate it using bash?
5+50*3/20 + (19*2)/7
I store it in a variable x. I can't figure out how to proceed.
You can create a simple script for that, let's call test.sh:
#!/bin/bash
x=$1
echo $(($x))
You can replace the line echo $(($x)) with perl -e "print $x" to get floating point output.
And execute it with your input:
$ test.sh "5+50*3/20 + (19*2)/7"
17
With floating point:
#!/bin/bash
x=$1
var=$(perl -e "print $x")
echo $var
The results:
$ test.sh "5+50*3/20 + (19*2)/7"
17.9285714285714
$ test.sh "-105+50*3/20 + (19^2)/7"
-95.0714285714286
You can just pass the expression to calc:
y = $(calc "$x")

Resources