Shebang expression - shebang

In one of my shell script(say abc.sh) I have below Sleeptime if condition, whenever sleeptime is passed as null in arguments, -1 is getting defaulted. this logic works fine in ksh but not in bash, reason being -eq relational operator does not work in bash while comparing null strings. to resolve this I have added shebang expression #!/bin/ksh at the beginning of my script.
if [ "$Sleeptime" -eq "" ]; then
Sleeptime=-1
fi
After adding shebang expression to my script, comparison and defaulting are working fine, but when my abc.sh is executed from other customs sh file, comparison & defaulting is not working, I tried adding shebang expression in the custom sh file also but still facing the issue. Any pointers please to resolve an issue.
custom sh file content:
#!/bin/ksh
cd /opt/common/config/
sh abc.sh $1 $2 $3
$3- is the sleeptime argument

Related

Awk command in Tcl foreach loop

I need to pass a variable value to awk command used in tickle.
Goal : i want to extract fisrt field of file clk_gate_names one by one in loop.So this is small example shown the same concept have benn used in one script.
one testcase below:
set a 0
incr a
foreach i {0.. 11} { # i variable is used for another thing
set test [exec awk {NR==${a}{print $1}} clk_gate_names]
}
Getting below error:
awk: NR==${a}{print $1}
awk: ^ syntax error
Please throw some light here. Thanks in advance :)
The problem is that you're wanting to mix variables from Tcl and variables from Awk. That's a bit complicated, as they are necessarily separate processes.
One option is to use Tcl's format command to build the Awk script-let.
set a 0
incr a
foreach i {0.. 11} { # i variable is used for another thing
set awkcode [format {NR==%d {print $1}} $a]
set test [exec awk $awkcode clk_gate_names]
}
However, a better approach is to pass the variable in by the -v option. That has the advantage of being not easily confused by any weird quoting going on, and Tcl doesn't break words unexpectedly (except, in exec, when it looks like a file/pipe redirection; that's a wart).
set a 0
incr a
foreach i {0.. 11} { # i variable is used for another thing
set test [exec awk -v a=$a {NR==a {print $1}} clk_gate_names]
}
The third way is to use an environment variable. That has the advantage of not being susceptible to the problems with redirections (so you can pass in values containing > for example).
set a 0
incr a
foreach i {0.. 11} { # i variable is used for another thing
set env(A_FROM_TCL) $a
set test [exec awk {NR==$ENVIRON["A_FROM_TCL"] {print $1}} clk_gate_names]
}
Notes:
Remember with awk and Tcl, don't use single quotes. Tcl doesn't use the same syntax as bash. Put the script in braces always or you'll get very confused.
I assume you know about the syntax of foreach? For the purposes of our discussion here it's not important, but you'd probably be having problems from it if the code you posted is exactly what you are using…
try below logic -
awk -v a=1 'NR==a {print $1}' f
As per your code -
set a 0
incr a
foreach i {0.. 11} { # i variable is used for another thing
set test [exec awk -v a="$a" 'NR==a {print $1}' clk_gate_names]
}
let me know if it satisfy your req. and share the output(desired output/error)
awk -v a=${a} "NR==a{print \$1}" filename
Check :
http://phaseit.net/claird/comp.lang.tcl/fmm.html
AWK: who | awk '{ print $1 }' works from the command line, but my Tcl interpreter rejects exec who | awk '{ print $1 }' I often hear
that from people who haven't yet learned to ask tclsh to interpret
exec who | awk {{ print $1 }} Notice that the '-s aren't "about" awk;
they're just conventional quoting in the shells from which one most
often accesses awk. exec doesn't use the shells. {} perform an
analogous function of quoting for Tcl. More examples appear in this
Google thread. Alexandre Ferrieux gives a correct and concise
explanation in another thread. The problem at hand for him happened to
do with sed, but that's essentially inconsequential; the command-line
syntaxes of awk and sed exhibit identical symptoms. Briefly, as the
error messages which arise themselves say, the single quotes (') are
the problem. These are shell markup for strings which shall not be
substituted. /bin/sh and others remove this markup before calling awk.
In Tcl, in contrast, braces {} serve the same purpose. Braces have the
advantage that they can be nested. One conclusion: brace the braces
that awk expects: awk -F " " {{print $1}}. Note that, in this last, it
is not necessary to escape the dollar-sign, because the outer brace
protect it, too.
Much the same has been written several times by Richard Suchenwirth,
Mark Janssen, Kevin Kenny, and others; it's unlikely any particular
expression was invented in isolation.

Bash array indirection in a function [duplicate]

Bash script to create multiple arrays from csv with unknown columns.
I am trying to write a script to compare two csv files with similar columns. I need it to locate the matching column from the other csv and compare any differences. The kicker is I would like the script to be dynamic to allow any number of columns to be entered and it still be able to function. I thought I had a good plan to solve this but turns out I'm running into syntax errors. Here is a sample of a csv I need to compare.
IP address, Notes, Nmap-SSH, Nmap-SMTP, Nmap-HTTP, Nmap-HTTPS,
10.0.0.1, , open, closed, open, open,
10.0.0.2, , closed, open, closed, closed,
When I read the csv file I was planning to look for "IF column == open; then; populate this column's array with the IP address" This would have given me 4 lists in this scenario with the IPs that were listening on said port. I could then compare that to my security device configuration to make sure it was configured properly. Finally to the meat, here is what I thought would accomplish creating the arrays for me to search later. However I ran into a snag when I tried to use a variable inside an array name. Can my syntax be corrected or is there just a better way to do this sort of thing?
#!/bin/bash
#
#
# This script compares config_cleaned_<ip>.txt output against ext_web_env.csv and outputs the differences
#
#
# Read from ext_web_env.csv file and create Array
#
FILENAME=./tmp/ext_web_env.csv
#
index=0
#
while read line
do
# How many columns are in the .csv?
varEnvCol=$(echo $line | awk -F, '{print NF}')
echo "columns = $varEnvCol"
# While loop to create array for each column
while [ $varEnvCol != 2 ]
do
# Checks to see if port is open; if so then add IP address to array
varPortCon=$(echo $line | awk -F, -v i=$varEnvCol '{print $i}')
if [ $varPortCon = "open" ]
then
arr$varEnvCol[$index]="$(echo $line | awk -F, '{print $1}')"
# I get this error message "line29 : arr8[194]=10.0.0.194: command not found"
fi
echo "arrEnv$varEnvCol is: ${arr$varEnvCol[#]}"
# Another error but not as important since I am using this to debug "line31: arr$varEnvCol is: ${arr$varEnvCol[#]}: bad substitution"
varEnvCol=$(($varEnvCol - 1))
done
index=$(($index + 1 ))
done < $FILENAME
UPDATE
I also tried using the eval command since all the data will be populated by other scripts.
but am getting this error message:
./compare.sh: line 41: arr8[83]=10.0.0.83: command not found
Here is my new code for this example:
if [[ $varPortCon = *'open'* ]]
then
eval arr\$varEnvCol[$index]=$(echo $line | awk -F, '{print $1}')
fi
arr$varEnvCol[$index]="$(...)"
doesn't work the way you expect it to - you cannot assign to shell variables indirectly - via an expression that expands to the variable name - this way.
Your attempted workaround with eval is also flawed - see below.
tl;dr
If you use bash 4.3 or above:
declare -n targetArray="arr$varEnvCol"
targetArray[index]=$(echo $line | awk -F, '{print $1}')
bash 4.2 or earlier:
declare "arr$varEnvCol"[index]="$(echo $line | awk -F, '{print $1}')"
Caveat: This will work in your particular situation, but may fail subtly in others; read on for details, including a more robust, but cumbersome alternative based on read.
The eval-based solution mentioned by #shellter in a since-deleted comment is problematic not only for security reasons (as they mentioned), but also because it can get quite tricky with respect to quoting; for completeness, here's the eval-based solution:
eval "arr$varEnvCol[index]"='$(echo $line | awk -F, '\''{print $1}'\'')'
See below for an explanation.
Assign to a bash array variable indirectly:
bash 4.3+: use declare -n to effectively create an alias ('nameref') of another variable
This is by far the best option, if available:
declare -n targetArray="arr$varEnvCol"
targetArray[index]=$(echo $line | awk -F, '{print $1}')
declare -n effectively allows you to refer to a variable by another name (whether that variable is an array or not), and the name to create an alias for can be the result of an expression (an expanded string), as demonstrated.
bash 4.2-: there are several options, each with tradeoffs
NOTE: With non-array variables, the best approach is to use printf -v. Since this question is about array variables, this approach is not discussed further.
[most robust, but cumbersome]: use read:
IFS=$'\n' read -r -d '' "arr$varEnvCol"[index] <<<"$(echo $line | awk -F, '{print $1}')"
IFS=$'\n' ensures that that leading and trailing whitespace in each input line is left intact.
-r prevents interpretation of \ chars. in the input.
-d '' ensures that ALL input is captured, even multi-line.
Note, however, that any trailing \n chars. are stripped.
If you're only interested in the first line of input, omit -d ''
"arr$varEnvCol"[index] expands to the variable - array element, in this case - to assign to; note that referring to variable index inside an array subscript does NOT need the $ prefix, because subscripts are evaluated in arithmetic context, where the prefix is optional.
<<< - a so-called here-string - sends its argument to stdin, where read takes its input from.
[simplest, but may break]: use declare:
declare "arr$varEnvCol"[index]="$(echo $line | awk -F, '{print $1}')"
(This is slightly counter-intuitive, in that declare is meant to declare, not modify a variable, but it works in bash 3.x and 4.x, with the constraints noted below.)
Works fine OUTSIDE a FUNCTION - whether the array was explicitly declared with declare or not.
Caveat: INSIDE a function, only works with LOCAL variables - you cannot reference shell-global variables (variables declared outside the function) from inside a function that way. Attempting to do so invariably creates a LOCAL variable ECLIPSING the shell-global variable.
[insecure and tricky]: use eval:
eval "arr$varEnvCol[index]"='$(echo $line | awk -F, '\''{print $1}'\'')'
CAVEAT: Only use eval if you fully control the contents of the string being evaluated; eval will execute any command contained in a string, with potentially unwanted results.
Understanding what variable references/command substitutions get expanded when is nontrivial - the safest approach is to delay expansion so that they happen when eval executes rather than immediate expansion that happens when arguments are passed to eval.
For a variable assignment statement to succeed, the RHS (right-hand side) must eventually evaluate to a single token - either unquoted without whitespace or quoted (optionally with whitespace).
The above example uses single quotes to delay expansion; thus, the string passed mustn't contain single quotes directly and thus is broken into multiple parts with literal ' chars. spliced in as \'.
Also note that the LHS (left-hand side) of the assignment statement passed to eval must be a double-quoted string - using an unquoted string with selective quoting of $ won't work, curiously:
OK: eval "arr$varEnvCol[index]"=...
FAILS: eval arr\$varEnvCol[index]=...

translation from bash to ash shell: how to handle arrays defined by input?

I try to transfer the excellent example docker-haproxy from centos to alpine.
A shell script is used to process a list of values given as parameters to the script into an array, then write these values plus their index to some file.
The following construction works in bash:
ServerArray=${SERVERS:=$1}
...
for i in ${ServerArray[#]}
do
echo " " server SERVER_$COUNT $i >> /haproxy/haproxy.cfg
let "COUNT += 1"
done
but not in ash (or sh):
syntax error: bad substitution
The error refers to line
for i in ${ServerArray[#]}
What is the correct syntax here? I guess the line
ServerArray=${SERVERS:=$1}
does not define an array as intended, but googling for long did not help me.
bash to sh (ash) spoofing says
sh apparently has no arrays.
If so, how to solve the problem then?
I guess I can do with this construction:
#!/bin/sh
# test.sh
while [ $# -gt 0 ]
do
echo $1
shift
done
delivers
/ # ./test 172.17.0.2:3306 172.17.0.3:3306
172.17.0.2:3306
172.17.0.3:3306
which is what I need to proceed

Program tester, bash

I'm trying to test a program (tp3) with several input files and printing the output in another file. So I've designed the following bash script name runner to do everything at the same time:
#!/bin/bash
rm $2
clear
FILES=(`ls ${1}`)
cmd='./tp3'
for f in ${FILES[*]}
do
echo "$f"
echo "--------------<$f>--------------" >> $2
$cmd < $1$f 2>> $2 >> $2
done
Everytime I run this script I get the following error:
./runner: line 10: $2: ambiguous redirect
./runner: line 11: testtest: No such file or directory
To run the bash script I do:
./runner test
What is wrong in the script?
Modifications to make it work:
First of all I've quoted the variables, then I've replaced the second argument "$2" for a file named "TEST" and now everything is working just fine.
New code:
#!/bin/bash
rm TEST
clear
FILES=(`ls *.in`)
cmd='./tp3'
for f in ${FILES[*]}
do
echo "$f"
echo "--------------<"$f">--------------" >> "TEST"
"$cmd" < "$1$f" >> "TEST" 2>> "TEST"
done
Thanks everyone for your help.
You are running ./runner test in which test is $1 and $2 is empty. Your redirection is therefor illegal. Also try to couple stdout and stderr when pointing to the same output. This can be done as follows: command arguments > output 2>&1. This will send stderr output to where ever the stdout output is sent.
Also, as Wintermute pointed out: quote variables. Spaces in variables will make it be interpreted as separate arguments. e.g. command $1 supplies two arguments to command if $1 equals some string for example.
This translates into the following: you use $f if this contains a space it will split the argument and everything after the space will be treated as extra arguments or commands rather than one single argument.

Create array in bash with variables as array name

I'm not sure if this has been answered, I've looked and haven't found anything that looks like what I'm trying to do. I also posted this to stackexchange (https://unix.stackexchange.com/questions/189293/create-array-in-bash-with-variables-as-array-name)
I have a number of shell scripts that are capable of running against a ksh or bash shell, and they make use of arrays. I created a function named "setArray" that interrogates the running shell and determines what builtin to use to create the array - for ksh, set -A, for bash, typeset -a. However, I'm having some issues with the bash portion.
The function takes two arguments, the name of the array and the value to add. This then becomes ${ARRAY_NAME} and ${VARIABLE_VALUE}. Doing the following:
set -A $(eval echo \${ARRAY_NAME}) $(eval echo \${${ARRAY_NAME}[*]}) "${VARIABLE_VALUE}"
works perfectly in ksh. However,
typeset -a $(eval echo \${ARRAY_NAME})=( $(eval echo \${${ARRAY_NAME}[*]}) "${VARIABLE_VALUE}" )
does not. This provides
bash: syntax error near unexpected token '('
I know I can just make it a list of strings (e.g. MYARRAY="one two three") and just loop through it using the IFS, but I don't want to lose the ability to use an array either.
Any thoughts ?
Given the assertion that the ksh portion of this function is working only the bash portion needs to be created. For which the following should work and, I believe, be safe and robust (though evidence to the contrary is welcome).
eval $ARRAY_NAME+=\(\"\$VARIABLE_VALUE\"\)
First expansion only expands $ARRAY_NAME to get
eval array+=("$VARIABLE_VALUE")
which eval then causes to be evaluated again normally.

Resources