Abnormal behavior when parsing an array - linux

Since yesterday I am facing with a strange behavior of shell code.
This is the code:
#!/bin/bash
operatori="/*-+="
temp=$1
len_temp=${#temp}
for (( i=0; i<$len_temp; i++ ))
do
array[i]=${temp:i:1}
#echo 'i= '${array[i]}
done
for i in ${array[#]}
do
if [[ "$operatori" =~ "$i" ]]; then
echo '##'$i
fi
done
It is executed with $1 = tom*jerry-1=0.
In this version of the code , i expect to return:
##*
##-
##=
But it returns just
##-
##=
On the other hand, I tried to deny the condition, having
if ! [[ "$operatori" =~ "$i" ]]; then
But the result is more strange:
##t
##o
##m
##analyzer.sh
##gnome-terminal.desktop
##mount location.sh
##test.sh
##j
##e
##r
##r
##y
##1
##0
Where I expect to receive:
##t
##o
##m
##j
##e
##r
##r
##y
##1
##0
where the analyzer.sh, gnome-teminal.desktop, mount location.sh, test.sh represents some files from the same location where my code is saved.
Can anyone tell me what am I doing wrong?

You should wrap variable references in double-quotes to prevent unexpected parsing oddities. In particular, use for i in "${array[#]}" and echo "##$i".
What's happening here is that in the for statement, the array expands to the equivalent of t o m * j e r r y - 1 = 0, which then undergoes word splitting (ok in this case) and wildcard expansion (which replaces the * with a list of files in the current directory), giving the equivalent of t o m analyzer.sh gnome-terminal.desktop 'mount location.sh' test.sh j e r r y - 1 = 0, which then causes the weird results you're seeing.
You could avoid this problem by setting the noglob shell option (as Kenavoz) suggested), but this will break any parts of the script that depend on wildcard expansion (and still leaves the potential for unexpected word splitting). It's better to just use double-quotes.

You can set bash to noglob as first command of your script to prevent globbing with * :
set -o noglob
Update :
Use set +o noglob to set noglob back to off when your script needs wildcard expansion :
set -o noglob
for i in ${array[#]}
do
if [[ "$operatori" =~ "$i" ]]; then
echo '##'$i
fi
done
set +o noglob
Note : #mikcutu, the noglob solution is a (working) workaround. See #Gordon Davisson's answer for details about why you first should double-quote your variables to prevent word splitting.

Related

Bash script : $x=$x+2 is not getting recognised

When I am executing the below script, I am getting the following error :-
The script executes infintely and below line is printed everytime.
"line 9: 1=1+2: command not found". Why?
#!/bin/bash
echo "Script 1 - Linux Scripting Book"
x=1
while [ $x -le 45 ]
do
echo x : $x
$x=$x+2
done
echo "End Of Script 1"
exit 0
Also if I change the $x=$x+2 to x+$x+2 then also I am getting the below error.
line 6: [: 1+2: integer expression expected
Same script when executed like this runs fine.
#!/bin/bash
echo "Script 1 - Linux Scripting Book"
x=1
while [ $x -le 45 ]
do
echo x : $x
let x=x+2
done
echo "End Of Script 1"
exit 0
You get line 9: 1=1+2: command not found because 1=1+2 is what $x=$x+2 is expanded into.
Use expr or let or ((...)) for integer calculations and bc for floating point:
let x=x+2
((x=x+2)) #same as above
((x+=2)) #same
((x++)) #if adding just one
((++x)) #if adding just one
x=$((x+2))
x=`expr $x + 2` #space before and after +
x=$(echo $x+2|bc) #using bc
x=$(echo $x+2.1|bc) #bc also works with floating points (numbers with decimals)
Since this part of the question isn't cleared yet, and not fine to post in a comment, I add this partial answer:
x=1; for i in 1 2 3 ; do x=$x+2; echo $x; done
1+2
1+2+2
1+2+2+2
As a side note: Don't use exit 0 at the end of your script without a good reason. When the script is done, it exits by itself without your help. The exit status will be the exit status of the last command performed, in your case a simple echo, which will almost always succeed. In the rare cases it fails, you will probably without intention hide that failure.
If you source the script, the exit will throw you out of your running shell.
But you can rewrite your while loop like this:
x=0
while (($((x)) < 9))
do
echo x : $x
x=$x+2
done
echo $((x))
x : 0
x : 0+2
x : 0+2+2
x : 0+2+2+2
x : 0+2+2+2+2
10
Because that's not the Bourne shell syntax for setting a variable; it looks more like Perl or PHP. The $ is used for parameter expansion and is not part of the variable name. Variable assignment simply uses =, and let evaluates arithmetic expressions (much like $((expression))). Another syntax that should work is x=$((x+2)). Note that these arithmetic evaluations are a bash feature; standard unix shells might require use of external tools such as expr.

Syntax error near unexpected token `done' whis done written

I have a problem with the done.
It says I have some typo error but I can't figure what's wrong at all.
Here is the code:
#./bin/bash
until [$err == 0];
do
java -Xms512m -Xmx512m -cp lib/*:lib/uMad/*:mysql-connector-java-5.1.15-bin.jar:l2jfrozen-core.jar com.l2jfrozen.gameserver.GameServer
err=$?
sleep 5
done
Your shebang line is wrong. #./bin/bash will not execute bash.
It should read #!/bin/bash. You are probably using a shell other than bash to invoke this script.
Also, beware that the [$err == 0] line expands the value of $err, which is probably an empty string, unless it has been exported. If it's empty, this will result in an error, because Bash will be interpreting [ == 0].
The safest approach is this:
unset err
until [ "$err" == "0" ];
do
# etc...
done
From my experience when working with brackets and if loops, you need proper spacing and double, not single brackets. There needs to be space on each side of the double brackets with the exception of the semi-colon. Here is an example block:
#!/bin/bash
err=5
until [[ $err == 0 ]]; do
((err-=1));
echo -e "$err\n";
sleep 3
done
I do not see why the same would not apply to a do until loop.
You're probably aware but your heading has a period in it instead of a shebang.
#./bin/bash

Understand the use of braces and parenthesis in if

In some of the code I was going through I found that if was using braces {} for someplace and parenthesis (()) for some other. Can someone tell me the exact meaning and where to use which one?
if [ "$1" = "--help" ]
if (( $# != 3 ))
The bracket [ is a built-in command of the shell; you can also call it as test:
if [ a = b ]
then ...
equals:
if test a = b
then ...
The syntax of the test command is rather text-oriented (see the bash man page for details at chapter CONDITIONAL EXPRESSIONS).
The braces {…} are shell syntax and used for grouping commands (without creating a subshell):
{ date; ls; echo $$; } > 1>&2
This will execute date, ls, and echo $$ and redirect all their output to stderr.
The parenthesis (…) are shell-syntax and used for creating a subshell:
(date; ls; echo $$) > 1>&2
Like above but the PID ($$) given out is that of the subshell.
The difference between grouping and subshell is delicate (and out of scope here).
The doubled brackets [[…]] are shell syntax but otherwise behave like the single bracket [ command. The only difference is for using < etc. for string comparison and locale support.
The doubled parentheses ((…)) are equivalent to using the let builtin shell command. They basically allow number-oriented expressions to be evaluated (ARITHMETIC EVALUATION). < and > sort numerically (instead of lexicographically) etc. Also, in some constructs like for ((i=0; i<10; i++)); do echo "$i"; done they are used as a fixed syntax.
Dollar-parenthesis $(…) result in the output of the command they enclose:
echo "$(date)" # a complicated way to execute date
Dollar-brackets $[…] are deprecated and should be replaced by dollar-double-parentheses.
Dollar-double-parentheses $((…)) result in the value of the numerical expression they enclose:
echo "$((4 + 3 * 2))" # should print 10
Dollar-braces ${…} result in the variable expansion they enclose. In the simplest case this is just a variable name, then they evaluate to the variable value:
a=foo
echo "${a}" # prints foo
This can (and often is) abbreviated by stripping the braces: $a
But it also can be more complex like ${a:-"today is $(date)"}. See Parameter Expansion in the bash man page for details.
Redirection-parenthesis <(…) and >(…) create a subprocess, a file descriptor its output/input is associated with, and a pseudo file associated with that descriptor. It can be used to pass the output of a program as a seeming file to another program: diff <(sleep 1; date) <(sleep 2; date)

Compare values in Linux

I'm using .conf which contain keys and values.
Some keys contains numbers like
deployment.conf
EAR_COUNT=2
EAR_1=xxx.ear
EAR_2=yyy.ear
When I try to retrieve that value using particular key and compare with integer value i.e. natural number.
But Whatever I retrieved values from .conf ,it is should be String datatype.
How should I compare both value in Linux Bash script.
Simply : How should I compare two values in Linux.?
Ex :
. ./deployment.conf
count=$EAR_COUNT;
echo "count : $count";
if [ $count -gt 0 ]; then
echo "Test"
fi
I'm getting following error :
count : 2
: integer expression expected30: [: 2
They're all strings in bash, notwithstanding your ability to do typeset-type things to flag them differently.
If you want to do numeric comparisons, just use -eq (or its brethren like -gt, -le) rather than ==, != and so on:
if [[ $num -eq 42 ]] ; then
echo Found the answer
fi
The full range of comparison operators can be found in the bash manpage, under CONDITIONAL EXPRESSIONS.
If you have something that you think should be a number and it's not working, I'll warrant it's not a number. Do something like:
echo "[$count]"
to make sure it doesn't have a newline at the end or, better yet, get a hex dump of it in case it holds strange characters, like Windows line endings:
echo -n $count | od -xcb
The fact that you're seeing:
: integer expression expected30: [: 2
with the : back at the start of the line, rather than the more usual:
-bash: [: XX: integer expression expected
tends to indicate the presence of a carriage return in there, which might be from deployment.conf having those Windows line endings (\r\n rather than the UNIXy \n).
The hex dump should make that obvious, at which point you need to go and clean up your configuration file.
Ref : http://linux.die.net/man/1/bash
-eq, -ne, -lt, -le, -gt, or -ge
These are arithmetic binary operators in bash scripting.
I have checked your code,
deployment.conf
# CONF FILE
EAR_COUNT=5
testArithmetic.sh
#!/bin/bash
. ./deployment.conf
count=$EAR_COUNT;
echo "count : $count";
if [ $count -gt 0 ]; then
echo "Test"
fi
running the above script evaluates to numeric comparison for fine. Share us your conf file contents, if you are facing any issues. If you are including the conf file in your script file, note the conf file must have valid BASH assignments, which means, there should be no space before and after '=' sign.
Also, you have mentioned WAR_COUNT=3 in conf part and used 'count=$EAR_COUNT;' in script part. Please check this too.
Most likely you have some non-integer character like \r in your EAR_COUNT variable. Strip all non-digits while assigning to count like this:
count=${EAR_COUNT//[^[:digit:]]/}
echo "count : $count";
if [[ $count -gt 0 ]]; then
echo "Test"
fi

Unable to have two-word-search in Zsh's TAB completion for Man

Problem: to have a tab completion which takes two words and calculates the best match from them for Man, and then returns the best matches
Example: The following pseudo-code should give me at least Zsh's reverse-menu-complete -command. Right now, I cannot search manuals inside manuals without zgrep.
man zsh:reverse <TAB>
where ":" is the separator which I want.
Initial Problem: Which files does the TAB completion run when I press TAB for one word in searching manuals by Zsh?
I will attempt to provide an insight to how zsh completion system works and an incomplete go at this problem.
The file that runs when you use TAB completion for man in zsh is located under the /usr/share/zsh/${ZSH_VERSION}/functions directory. The tree varies across distributions, but the file is named _man, and provides completion for man, apropos and whatis.
After _man is invoked, it works as following (rough description):
if completing for man and --local-file was specified as first flag, invoke standard files completion (_files)
construct manpath variable from a set of defaults / $MANPATH. This is where the manpages will be searched
determine if we invoked man with a section number parameter, if yes - only those sections will be searched
if the zstyle ':completion:*:manuals' separate-sections true was used, separate sections in output (don't mix between them)
invoke _man_pages to provide a list of man pages for the match
_man_pages now does a bit of magic with compfiles -p pages '' '' "$matcher" '' dummy '*'. pages is the variable with all the directories containing manpages for requested section(s). The actual globbing pattern is constructed from the implicit parameter $PREFIX and the last parameter to compfiles - * in this case. This results in /usr/share/man/man1 to be transformed into /usr/share/man/man1/foo*
The new list of glob patterns is globbed, obtaining all files which match the pattern
_man_pages then strips any suffixes from the files and adds them to the completion widget list of choices by using compadd
Now, as you can see, the list of manpages is directly determined by $PREFIX variable. In order to make zsh:foo to list only man pages of zsh* which contain the word foo, it needs to be split across : character (if any).
The following addition in _man_pages partially solve the issue (zsh 4.3.4):
Original:
_man_pages() {
local matcher pages dummy sopt
zparseopts -E M+:=matcher
if (( $#matcher )); then
matcher=( ${matcher:#-M} )
matcher="$matcher"
else
matcher=
fi
pages=( ${(M)dirs:#*$sect/} )
compfiles -p pages '' '' "$matcher" '' dummy '*'
pages=( ${^~pages}(N:t) )
(($#mrd)) && pages[$#pages+1]=($(awk $awk $mrd))
# Remove any compression suffix, then remove the minimum possible string
# beginning with .<->: that handles problem cases like files called
# `POSIX.1.5'.
[[ $OSTYPE = solaris* ]] && sopt='-s '
if ((CURRENT > 2)) ||
! zstyle -t ":completion:${curcontext}:manuals.$sect" insert-sections
then
compadd "$#" - ${pages%.(?|<->*(|.gz|.bz2|.Z))}
else
compadd "$#" -P "$sopt$sect " - ${pages%.(?|<->*(|.gz|.bz2|.Z))}
fi
}
Modified (look for ##mod comments):
_man_pages() {
local matcher pages dummy sopt
zparseopts -E M+:=matcher
if (( $#matcher )); then
matcher=( ${matcher:#-M} )
matcher="$matcher"
else
matcher=
fi
pages=( ${(M)dirs:#*$sect/} )
##mod
# split components by the ":" character
local pref_words manpage_grep orig_prefix
# save original prefix (just in case)
orig_prefix=${PREFIX}
# split $PREFIX by ':' and make it an array
pref_words=${PREFIX//:/ }
set -A pref_words ${=pref_words}
# if there are both manpage name and grep string, use both
if (( $#pref_words == 2 )); then
manpage_grep=$pref_words[2]
# PREFIX is used internally by compfiles
PREFIX=$pref_words[1]
elif (( $#pref_words == 1 )) && [[ ${PREFIX[1,1]} == ":" ]]; then
# otherwise, prefix is empty and only grep string exists
PREFIX=
manpage_grep=$pref_words[1]
fi
compfiles -p pages '' '' "$matcher" '' dummy '*'
##mod: complete, but don't strip path names
pages=( ${^~pages} )
(($#mrd)) && pages[$#pages+1]=($(awk $awk $mrd))
##mod: grep pages
# Build a list of matching pages that pass the grep
local matching_pages
typeset -a matching_pages
# manpage_grep exists and not empty
if (( ${#manpage_grep} > 0 )); then
for p in ${pages}; do
zgrep "${manpage_grep}" $p > /dev/null
if (( $? == 0 )); then
#echo "$p matched $manpage_grep"
matching_pages+=($p)
fi
done
else
# there's no manpage_grep, so all pages match
matching_pages=( ${pages} )
fi
#echo "\nmatching manpages: "${matching_pages}
pages=( ${matching_pages}(N:t) )
# keep the stripped prefix for now
#PREFIX=${orig_prefix}
# Remove any compression suffix, then remove the minimum possible string
# beginning with .<->: that handles problem cases like files called
# `POSIX.1.5'.
[[ $OSTYPE = solaris* ]] && sopt='-s '
if ((CURRENT > 2)) ||
! zstyle -t ":completion:${curcontext}:manuals.$sect" insert-sections
then
compadd "$#" - ${pages%.(?|<->*(|.gz|.bz2|.Z))}
else
compadd "$#" -P "$sopt$sect " - ${pages%.(?|<->*(|.gz|.bz2|.Z))}
fi
}
However, it's still not fully working (if you uncomment the #echo "$p matched $manpage_grep" line, you can see that it does build the list) - I suspect that somewhere internally, the completion system sees that, for instance, "zshcompctl" is not matched by prefix "zsh:foo", and does not display the resulting matches. I've tried to keep $PREFIX as it is after stripping the grep string, but it still does not want to work.
At any rate, this at least should get you started.

Resources