Grep for a dollar sign within backticks - linux

I have a file like this
File name : hello.txt
{1:ABC}{2:BCD}{3:{108:20140619-2}}{4:
:97A::Hi//12345
:97A::Hi//12345
:93B::Hello//FAMT/00000,
:16S:FIN
-}{5:{CHK:BDG6789}}{S:{ABC:}{DEF:S}{WOM:ONHGRT}}
Now basically i'm checking for the existence of $ symbol and as well as :97A: AND im using the below if statement.
if [ `grep -c '\$' hello.txt` -gt 0 ] && [ `grep -c ":97A:" hello.txt` -gt 1 ]
then
echo "condition satisfied"
else
echo "condition not satisfied"
fi
if i execute this im getting condition satisifed echo statement. But id should be the other way round :( since im putting AND condition. Please help on this.

I also don't understand what you're asking, but from your code I conclude that you have troubles grepping for the dollar sign. I guess you need to escape the backslash as well if you use backticks:
$ echo 'foo$bar' > dollar.txt
$ echo 'foo_bar' > no_dollar.txt
$ [ `grep -c '\$' dollar.txt` -gt 0 ] && echo 1
1
$ [ `grep -c '\$' no_dollar.txt` -gt 0 ] && echo 1
1
$ [ `grep -c '\\$' dollar.txt` -gt 0 ] && echo 1
1
$ [ `grep -c '\\$' no_dollar.txt` -gt 0 ] && echo 1
$ [ `grep -c '\\$' no_dollar.txt` -gt 0 ] || echo 0
0
alternatively, use $() instead of backticks

Replace grep -c '\$' hello.txt by grep -c '\\$' hello.txt Then it will work as desired.
ex:
bash -x test.sh
++ grep -c '\$' hello.txt
+ '[' 0 -gt 0 ']'
+ echo 'condition not satisfied'
condition not satisfied
PS: bash -x is your friend :)

I recommend executing your grep commands in a subshell with the $ syntax, then doing the comparison. In my opinion this is the cleaner way and requires you to be less of an escape artist.
if [ $(grep -c '\$' hello.txt) -gt 0 ] && [ $(grep -c ":97A:" hello.txt) -gt 1 ]
then
echo "condition satisfied"
else
echo "condition not satisfied"
fi
For your hello.txt the output will be:
>> bash test.bash
condition not satisfied
Since there's no dollar sign in your file
[ $(grep -c '\$' hello.txt) -gt 0 ]
will test
[ 0 -gt 0 ]
and yield false, while
[ $(grep -c ':97A' hello.txt) -gt 1 ]
will test
[ 2 -gt 1 ]
and yield true. Finally, false && true will yield false and the second echo statement will be executed.

"i'm checking for the existence of $ symbol"
First condition won't match because there is no "$" sign anywhere in your input, therefore output of first grep is 0. As 0 isn't greater than 0, the result is "false". Consequently, the second clause won't be executed at all. "Condition is not satisfied" because your requirement for "satisfied" is: input contains both "$" AND ":97A:".
For a result whether grep matched any line, you don't need to count number of matches.
if grep -q '\$' file; then ...
is a way to use result of grep in a conditional statement without rube-goldbergismns

Using awk and reading the file only once:
if awk '/[$]/{d++} /:97A:/{o++} END{exit !(d && o>1)}' hello.txt; then
echo "condition satisfied"
else
echo "condition not satisfied"
fi

Related

How to write "if grep a and grep b" structure in bash?

I write "if grep a and grep b" structure
if [ grep -q Bug test.txt ] && [ grep -q Ver test.txt ]
then
echo "found"
else
echo "not found"
fi
But the result is wrong, what is the true grammar of the code?
The wrong result:
./test.sh: line 2: [: too many arguments
not found
The condition in a shell if is a command, so
if grep -q Bug test.txt && grep -q Ver test.txt; then
echo "found"
else
echo "not found"
fi
This holds true even for if [ foo -eq bar ]; [ is a command (an alias for test) that returns 0 (which is by convention true in the shell) if the condition it is asked to evaluate is true. grep returns 0 if it finds a match and a non-zero value (by convention false in the shell) otherwise, so if grep -q foo bar is fine.

Having problems with my bash script

#!/bin/bash
sum=0
for number in $#
do
echo $number | grep -q "[^a-z]" >> /dev/null
if [ $? != 0 ]
then
echo "Sorry, '$number' is not a number"
else
sum=$((sum + number))
echo "$sum"
fi
done
My assignment requires that if I type add2 4 -3 12 9 it would output
22
but mine outputs:
4 1 3 22
And if I type add2 4 -3 twelve nine it would output:
Sorry, 'twelve' is not a number
but mine outputs
4
1
Sorry, 'twelve' is not a number
Sorry, 'nine' is not a number
My assignment requires echo $number | grep -q "[^a-z]" >> /dev/null as it wants me to redirect the output to /dev/null, I have no idea what I'm doing wrong.
The problem is that you are printing $sum after each number that is processed, whereas your assignment is to print it only after the last number. So you need to move the echo "$sum" part (which prints $sum) after the loop.
In other words, you need to change this:
echo "$sum"
fi
done
to this:
fi
done
echo "$sum"
my assignment requires echo $number | grep -q "[^a-z]" >> /dev/null as it wants me to redirect the output to /dev/null, […]
That's a bit redundant. grep -q doesn't print anything to standard output, so there's no reason to redirect its standard output. (And anyway, this isn't a good way to detect a valid number. This approach will accept $number as a valid number if it contains even one character that's not a lowercase letter. For example, it would accept ab%AB as a valid number.)
Edited to add: O.K., so how can you detect a valid number? This is surprisingly tricky.
It's possible to do it by reaching your own decision about what a valid number should look like, and then crafting grep-commands or similar that detect numbers in this form; for example, you might write any of these:
grep -q $'^-[1-9][0-9]*$\n^0$\n^[1-9][0-9]*$ <<< "$number"
grep -q '^-[1-9][0-9]*$' <<< "$number" \
|| grep -q '^[1-9][0-9]*$' <<< "$number" \
|| grep -q '^0$' <<< "$number"
[[ "$number" = 0 ]] || [[ "$number" =~ ^-?[1-9][0-9]*$ ]]
but I think it's better to "cheat", by asking Bash whether it recognizes the number, and whether it agrees that the number is input in the normal way:
[[ "$number." = "$(printf %d "$number" 2>/dev/null && echo .)" ]]
Incidentally, note that with any of these approaches, there's no reason to examine $? explicitly. The whole point of if is that it runs a command and examines its exit-status. Any Bash snippet of this form:
foo
if [ $? != 0 ]
then
bar
else
baz
fi
can also be written in this form:
if ! foo ; then
bar
else
baz
fi

Bash scripts errors

I'm trying to run those scripts but I keep receiving errors messages:
1-
#!/bin/bash
filename=$1
if [ -f $filename ]
then
owner=`stat -c %U $filename`
grep $owner /etc/passwd
if [ $? -eq 0 ]; then
perm=`stat -c %a $filename | head -c 1`
if [ $perm -gt 3 ]; then
cat $filename | grep NOTE
fi
fi
fi
the error message is :
stat: missing operand Try `stat --help' for more information.
2-
#!/bin/bash
NoSum=$1
sum=0
echo "Please enter $NoSum values one at a time"
for (( i=1; i<=$NoSum; i++ ))
do
echo "Next Value?"
read num
let "a = $sum + $num"
sum=$a
done
echo "The sum is : $sum"
the error message is:
Please enter values one at a time ./scr3: line 6: ((: i<=: syntax
error: operand expected (error token is "<=") The sum is : 0
3-
#!/bin/bash
dir=$1
if [ -d $dir ]
then
perm=`stat -c %a $dir | head -c 1`
if [ $perm -gt 5 ]; then
cd $dir
for file in $dir/*
do
if ! [ -x "$file" ]
then
echo "$file"
fi
done
fi
fi
the error message is:
stat: missing operand Try `stat --help' for more information. ./scr4:
line 8: [: -gt: unary operator expected
any idea how to fix them ?
Nothing is wrong about the programs.You are not supplying the command line arguments.You must run it as
1 and 3:
./script.sh <filename>
2:
./script.sh <number>
$1 stands for the first command line argument
You need to quote variables in bash to prevent word-splitting issues, both in test brackets [] and most of the time in other use.
So your first script would be
#!/bin/bash
filename="$1"
if [ -f "$filename" ]
then
owner="`stat -c %U "$filename"`"
grep "$owner" /etc/passwd
if [ $? -eq 0 ]; then
perm="`stat -c %a "$filename" | head -c 1`"
if [ "$perm" -gt 3 ]; then
cat "$filename" | grep NOTE
fi
fi
fi
The others have similar erros

In bash, how to store a return value in a variable?

I know some very basic commands in Linux and am trying to write some scripts. I have written a function which evaluates the sum of last 2-digits in a 5-digit number. The function should concatenate this resultant sum in between the last 2-digits and return it. The reason I want to return this value is because I will be using this value in the other function.
Ex: if I have 12345, then my function will calculate 4+5 and return 495.
#!/bin/bash
set -x
echo "enter: "
read input
function password_formula
{
length=${#input}
last_two=${input:length-2:length}
first=`echo $last_two| sed -e 's/\(.\)/\1 /g'|awk '{print $2}'`
second=`echo $last_two| sed -e 's/\(.\)/\1 /g'|awk '{print $1}'`
let sum=$first+$second
sum_len=${#sum}
echo $second
echo $sum
if [ $sum -gt 9 ]
then
sum=${sum:1}
fi
value=$second$sum$first
return $value
}
result=$(password_formula)
echo $result
I am trying to echo and see the result but I am getting the output as shown below.
-bash-3.2$ ./file2.sh
+++ password_formula
+++ echo 'enter: '
+++ read input
12385
+++ length=8
+++ last_two=85
++++ echo 85
++++ sed -e 's/\(.\)/\1 /g'
++++ awk '{print $2}'
+++ first=5
++++ echo 85
++++ sed -e 's/\(.\)/\1 /g'
++++ awk '{print $1}'
+++ second=8
+++ let sum=5+8
+++ sum_len=2
+++ echo 5
+++ echo 8
+++ echo 13
+++ '[' 13 -gt 9 ']'
+++ sum=3
+++ value=835
+++ return 835
++ result='enter:
5
8
13'
++ echo enter: 5 8 13
enter: 5 8 13
I also tried to print the result as:
password_formula
RESULT=$?
echo $RESULT
But that is giving some unknown value:
++ RESULT=67
++ echo 67
67
How can I properly store the correct value and print (to double check) on the screen?
Simplest answer:
the return code from a function can be only a value in the range from 0 to 255 .
To store this value in a variable you have to do like in this example:
#!/bin/bash
function returnfunction {
# example value between 0-255 to be returned
return 23
}
# note that the value has to be stored immediately after the function call :
returnfunction
myreturnvalue=$?
echo "myreturnvalue is "$myreturnvalue
The return value (aka exit code) is a value in the range 0 to 255 inclusive. It's used to indicate success or failure, not to return information. Any value outside this range will be wrapped.
To return information, like your number, use
echo "$value"
To print additional information that you don't want captured, use
echo "my irrelevant info" >&2
Finally, to capture it, use what you did:
result=$(password_formula)
In other words:
echo "enter: "
read input
password_formula()
{
length=${#input}
last_two=${input:length-2:length}
first=`echo $last_two| sed -e 's/\(.\)/\1 /g'|awk '{print $2}'`
second=`echo $last_two| sed -e 's/\(.\)/\1 /g'|awk '{print $1}'`
let sum=$first+$second
sum_len=${#sum}
echo $second >&2
echo $sum >&2
if [ $sum -gt 9 ]
then
sum=${sum:1}
fi
value=$second$sum$first
echo $value
}
result=$(password_formula)
echo "The value is $result"
It is easy you need to echo the value you need to return and then capture it like below
demofunc(){
local variable="hellow"
echo $variable
}
val=$(demofunc)
echo $val
Use the special bash variable "$?" like so:
function_output=$(my_function)
function_return_value=$?
The answer above suggests changing the function to echo data rather than return it so that it can be captured.
For a function or program that you can't modify where the return value needs to be saved to a variable (like test/[, which returns a 0/1 success value), echo $? within the command substitution:
# Test if we're remote.
isRemote="$(test -z "$REMOTE_ADDR"; echo $?)"
# Or:
isRemote="$([ -z "$REMOTE_ADDR" ]; echo $?)"
# Additionally you may want to reverse the 0 (success) / 1 (error) values
# for your own sanity, using arithmetic expansion:
remoteAddrIsEmpty="$([ -z "$REMOTE_ADDR" ]; echo $((1-$?)))"
E.g.
$ echo $REMOTE_ADDR
$ test -z "$REMOTE_ADDR"; echo $?
0
$ REMOTE_ADDR=127.0.0.1
$ test -z "$REMOTE_ADDR"; echo $?
1
$ retval="$(test -z "$REMOTE_ADDR"; echo $?)"; echo $retval
1
$ unset REMOTE_ADDR
$ retval="$(test -z "$REMOTE_ADDR"; echo $?)"; echo $retval
0
For a program which prints data but also has a return value to be saved, the return value would be captured separately from the output:
# Two different files, 1 and 2.
$ cat 1
1
$ cat 2
2
$ diffs="$(cmp 1 2)"
$ haveDiffs=$?
$ echo "Have differences? [$haveDiffs] Diffs: [$diffs]"
Have differences? [1] Diffs: [1 2 differ: char 1, line 1]
$ diffs="$(cmp 1 1)"
$ haveDiffs=$?
$ echo "Have differences? [$haveDiffs] Diffs: [$diffs]"
Have differences? [0] Diffs: []
# Or again, if you just want a success variable, reverse with arithmetic expansion:
$ cmp -s 1 2; filesAreIdentical=$((1-$?))
$ echo $filesAreIdentical
0
It's due to the echo statements. You could switch your echos to prints and return with an echo. Below works
#!/bin/bash
set -x
echo "enter: "
read input
function password_formula
{
length=${#input}
last_two=${input:length-2:length}
first=`echo $last_two| sed -e 's/\(.\)/\1 /g'|awk '{print $2}'`
second=`echo $last_two| sed -e 's/\(.\)/\1 /g'|awk '{print $1}'`
let sum=$first+$second
sum_len=${#sum}
print $second
print $sum
if [ $sum -gt 9 ]
then
sum=${sum:1}
fi
value=$second$sum$first
echo $value
}
result=$(password_formula)
echo $result
Something like this could be used, and still maintaining meanings of return (to return control signals) and echo (to return information) and logging statements (to print debug/info messages).
v_verbose=1
v_verbose_f="" # verbose file name
FLAG_BGPID=""
e_verbose() {
if [[ $v_verbose -ge 0 ]]; then
v_verbose_f=$(tempfile)
tail -f $v_verbose_f &
FLAG_BGPID="$!"
fi
}
d_verbose() {
if [[ x"$FLAG_BGPID" != "x" ]]; then
kill $FLAG_BGPID > /dev/null
FLAG_BGPID=""
rm -f $v_verbose_f > /dev/null
fi
}
init() {
e_verbose
trap cleanup SIGINT SIGQUIT SIGKILL SIGSTOP SIGTERM SIGHUP SIGTSTP
}
cleanup() {
d_verbose
}
init
fun1() {
echo "got $1" >> $v_verbose_f
echo "got $2" >> $v_verbose_f
echo "$(( $1 + $2 ))"
return 0
}
a=$(fun1 10 20)
if [[ $? -eq 0 ]]; then
echo ">>sum: $a"
else
echo "error: $?"
fi
cleanup
In here, I'm redirecting debug messages to separate file, that is watched by tail, and if there is any changes then printing the change, trap is used to make sure that background process always ends.
This behavior can also be achieved using redirection to /dev/stderr, But difference can be seen at the time of piping output of one command to input of other command.
Ok the main answers to this are problematic if we have errexit set, e.g.
#!/bin/bash
set -o errexit
my_fun() {
# returns 0 if the first arguments is "a"
# 1 otherwise
[ "${1}" = "a" ]
}
my_fun "a"
echo "a=$?"
my_fun "b"
echo "b=$?"
In this case bash just exit when the result is not 0, e.g. this only prints the a line.
./test_output.sh
a=0
As already said well here probably the most correct answer is something like this:
# like this
my_val=0 ; my_fun "a" || my_val=$?
echo "a=${my_val}"
# or this
my_fun "b" && my_val=0 || my_val=$?
echo "b=${my_val}"
This print all the values correctly without error
a=0
b=1
I don't know if the "echo" implementation is the most correct, as I still is not clear to me if
$() creates a subshell or not.
I have a function for example that opens up a
file descriptor in a function and return the number and it seems that bash closes the fd after exiting the function.
(if someone can help me here :-)

A case in which both test -n and test -z are true

#! /bin/bash
echo "Please input 2 nums: "
read a b
if [ -z $b ]; then
echo b is zero !
fi
if [ -n $b ]; then
echo b is non-zero !
fi
when run the script, only input 1 number, and leave the other empty, then b is supposed to be null. but the result is both echo is printed.
-laptop:~$ ./test.sh
Pleaes input 2 nums:
5
b is zero !
b is non-zero !
b is both null and non-null ?! Could anyone comment on this ? Thanks !
~
Replace
if [ -z $b ]; then
with
if [ -z "$b" ]; then
And do the same in the other if condition as well.
See http://tldp.org/LDP/abs/html/testconstructs.html for some interesting tests.
It's all in the quotes. I don't remember where, but someone explained this recently on SO or USE - Without the quotes it doesn't actually do an empty/non-empty string test, but just checks that -n or -z are non-empty strings themselves. It's the same test that makes this possible:
$ var=-n
$ if [ "$var" ]
then
echo whut
fi
Returns whut.
This means you can also have a sort of functional programming:
$ custom_test() {
if [ "$1" "$2" ]
then
echo true
else
echo false
fi
}
$ custom_test -z "$USER"
false
$ custom_test -n "$USER"
true
The -n test requires that the string be quoted within the test brackets. Using an unquoted string with ! -z, or even just the unquoted string alone within test brackets normally works, however, this is an unsafe practice. Always quote a tested string.
$ b=''
$ [ -z $b ] && echo YES # after expansion: `[ -z ] && echo YES` <==> `test -z && echo YES`
YES
$ [ -n $b ] && echo YES # after expansion: `[ -n ] && echo YES` <==> `test -n && echo YES`
YES
test against nothing, yield true.

Resources