Script with multiline string and for loop - bash

Writing a quick script for a temporary/repetitive task. Wrote a basic solution that works:
for thing in "$#";
do
/usr/mysql/bin/mysql -u xyz -p pdq <<END;
UPDATE table
SET table_atr = 'NW'
WHERE record_id = $thing
END
done
This works but forces a password check for every member of argument array (not ideal).
Tried to update it to this:
if {$# -le 1}; then
for thing in "$#";
do
/usr/mysql/bin/mysql -u xyz -p pdq <<END;
UPDATE table
SET table_atr = 'NW'
WHERE record_id = $thing
END
done
else
things = ""
for thing in "$#";
do
things += "$thing"
if {$thing == $#[$# - 1]}; then
things += "\n"
continue
else
things += ",\n"
done
/usr/mysql/bin/mysql -u xyz -p pdq <<END;
UPDATE table
SET table_atr = 'NW'
WHERE record_id IN
(
$things
)
END
TLDR: If there is more than one argument: do a for loop to fill a WHERE .. IN () statement. I realize this doesn't even need to be a multiline string and maybe that is my issue but the error I'm getting is (apparently) unrealted .
The error I get is:
line 24: syntax error near unexpected token' done'
line 24: ' done'
Neither I, nor my supervisor have much experience with shell scripts but I cannot see any syntax error with the 2nd for loop. Its exactly the same as the first which executes fine.
Any help is greatly appreciated, I may just have to go to the basic version or write this as a Perl script instead. Thanks!

Thanks everyone for all the advice. This was an edit that worked:
#!/bin/sh
if [ $# -le 1 ]; then
for thing in "$#";
do
/usr/mysql/bin/mysql -u xyz -p pdq <<END;
UPDATE table
SET table_atr = 'NW'
WHERE record_id = $thing
END
done
else
things=""
i=1
for thing in "$#";
do
things+="$thing"
if [ $i -eq $# ]; then
things+=""
else
things+=", "
fi
((i+=1))
done
/usr/mysql/bin/mysql -u xyz -p pdq <<END;
UPDATE table
SET table_atr = 'NW'
WHERE record_id IN ($things)
END
fi
There were indeed many syntax errors and changing the WHERE .. IN () string construction to a single line made this a lot easier. Luckily I didn't have to worry about inserting single quotes, mysql took the query without them.
I came out of this with a much higher respect for bash scripting. It is a serious language that requires its own study and I will approach it with much more attention to detail in the future.
Thanks again.

Related

Perl one liner in Bash script

I have a bash script that runs, and I'm trying to use a Perl one-liner to replace some text in a file variables.php
However, I would like to check if the Perl one-liner runs successfully and that's where I get hung up. I could just output the one-liner and it would work fine, but I would like to know for sure that it ran.
Basically, the function replace_variables() is the function that does the update, and it's the if statement there that I would like to check if my one-liner worked properly.
I've tried using the run_command function in that if statement, but that did not work, and I've tried putting the one-liner directly there, which also didn't work.
If I don't wrap it in an if statement, and just call the one-liner directly, everything works as intended.
here's the full file
#!/bin/bash
export CLI_CWD="$PWD"
site_variables() {
if [ -f "$CLI_CWD/variables.php" ]; then
return true
else
return false
fi
}
replace_variables() {
# perl -pi -e 's/(dbuser)(\s+)=\s.*;$/\1 = Config::get("db")["user"];/; s/(dbpass)(\s+)=\s.*;$/\1 = Config::get("db")["pass"];/; s/(dbname)(\s+)=\s.*;$/\1 = Config::get("db")["database"];/' "$CLI_CWD/variables.php"
if [run_command ]; then
echo "Updated variables.php successfully"
else
echo "Did not update variables.php"
fi
}
run_command() {
perl -pi -e 's/(dbuser)(\s+)=\s.*;$/\1 = Config::get("db")["user"];/; s/(dbpass)(\s+)=\s.*;$/\1 = Config::get("db")["pass"];/; s/(dbname)(\s+)=\s.*;$/\1 = Config::get("db")["database"];/' "$CLI_CWD/variables.php"
}
if [ site_variables ]; then
replace_variables
else
>&2 echo "Current directory ($(pwd)) is not a project root directory"
exit 4
fi
here's the function where the if statement fails
replace_variables() {
# perl -pi -e 's/(dbuser)(\s+)=\s.*;$/\1 = Config::get("db")["user"];/; s/(dbpass)(\s+)=\s.*;$/\1 = Config::get("db")["pass"];/; s/(dbname)(\s+)=\s.*;$/\1 = Config::get("db")["database"];/' "$CLI_CWD/variables.php"
if [run_command ]; then
echo "Updated variables.php successfully"
else
echo "Did not update variables.php"
fi
}
You can see that I commented out the one-liner just before the if statement, it works if I let that run and remove the if/else check.
here is the original file snippet before the update
//Load from Settings DB
$dbuser = 'username';
$dbpass = 'password';
$dbname = 'database_name';
here is the file snippet after the update would run
//Load from Settings DB
$dbuser = Config::get("db")["user"];
$dbpass = Config::get("db")["pass"];
$dbname = Config::get("db")["database"];
tl;dr and Solution
This usage of if with [ ] will not give you the result you expect.
What you're looking for
...
if run_command; then
...
Longer explanation
Basics of if
if is a shell feature
based on the condition, it executes the body contained in between then and fi
the "condition" that if checks is a command
commands usually have a return/exit code. typically
0 for success
1 (common) and everything else for some error
e.g. 127 for command not found
when the return/exit code is 0, the body is executed
otherwise it is skipped; or control is passed to elif or else
the syntax is if <command>; then...
Where does that [ ] come from?
test is a command that can check file types and compare values
refer man test and help test (bash only)
[ ... ] is a synonym for test
NB the brackets should be surrounded by spaces on both sides
if [ -f "/path/to/$filename" ]; then
exception: when terminated by new line or ; space not required
test (or [ ]) evaluates expressions and cannot execute other commands or functions
if [ expr ]; then is alternate syntax for if test expr; then
PS: good practice to "quote" your "$variables" when used with test or [ ]
PPS: [[ ... ]] is a different thing altogether. not POSIX; available only in some shells. take a look at this thread on the UNIX Stack Exchange

Cannot evaluate script arguments from function

I've started writing shell scripts again and I've found myself in a situation where I frequently have to write debug echo's to trace what the script is doing. The, easy, way I used to do this right was to write something like this :
#!/bin/bash
myVar = 'Erractic Nonesense'
echo "myVar: $myVar"
==> myVar: Erractic Nonesense
This worked great and was fairly simple but, having to write this for every variable I wished to trace was tiring and as a person who thinks that having less code to do more stuff is great, I wrote myself a function:
#!/bin/bash
dbg() # $msg
{
echo "$#: ${!#}"
}
myVar = 'Erractic Nonesense'
dbg myVar
==> myVar: Erractic Nonesense
This works great for regular variables but, for the scripts arguments ($1, $2, etc.), does not work. Why?
==> $ ./myScript 123
#!/bin/bash
...
dbg 1 # This is the bugger in question.
==> 1: 1
And also, how can this be circumvented?
EDIT
Thanks to Barmar I now see why it behaves this way but, the second question remains.
EDIT 2
Using koodawg idea, this is the result. It works. Updated, see EDIT 4
EDIT 3
I think that a mix of EDIT 2 and set +-x will be a viable solution.
EDIT 4
Updated the logic to fall on arguments as the previous one did not always worked. Added fancy tabs.
RX_INTEGER='^[0-9]+$'
DBG_SCRIPT_ARGS=( "$0" "$#" )
DBG_PADDING=" " # tabs of 8 spaces
dbg() # $msg | OUT$args OUT$res
{
args=$#
[[ $args =~ $RX_INTEGER ]] && res="${DBG_SCRIPT_ARGS[args]}" || res="${!#}"
printf "%s%s\`%s\`\n" "$args:" "${DBG_PADDING:$(((${#args}-1)%${#DBG_PADDING}))}"
}
You would have to call the function and explicitly pass the script args to it. You could do something like:
for argz in `echo $*`
do
dbg ${argz}
done

Ksh function to query Oracle with return values

Some time ago I wrote a small routine to run some quick n' dirty queries (and with that I mean it is not used for large queries) against an Oracle DB, but also wanted to do it a bit easier to parse errors. Follows:
# Executes the query
#
# Will execute a query contained in the variable named
# in the parameter $4 and store the result in the variable
# named in $5.
# In case of errors (even SQL related) the function should
# exit with status 1, making it possible to "if execQuery".
#
# #param $1 = User
# $2 = Pasword
# $3 = Tns Alias
# $4 = Name of the variable containing the query
# $5 = Name of the variable to hold the result
#
# #return query execution status
function execQuery {
typeset eSQLU=$1
typeset eSQLP=$2
typeset eSQLS=$3
typeset etQUERY=$4
eval typeset eQUERY=\$$etQUERY
typeset eQRES=$5
logMessageFile "DEBUG" "Query: $eQUERY"
typeset res=$(sqlplus -s $eSQLU/$eSQLP#$eSQLS <<EOF
set echo off newpage 0 space 0 pagesize 0 feed off head off verify off lines 999
WHENEVER SQLERROR EXIT 1
$eQUERY
exit;
EOF
)
[[ $? -gt 0 ]] && return 1 || eval "$eQRES=\"$res\""
}
The idea of this function is that later I could do something like:
query="select sysdate from dual;"
if execQuery $RAID_APP_PI_USR $RAID_APP_PI_PWD $RAID_APP_PI_SID query result ; then
echo $result
logMessageFile "INFO" "Inserts into XX successful."
else
logMessageFile "ERROR" "Error insertando XXX."
fi
It kinda works... A properly written query will do it fine, and the result variable is all correctly evaluated and all. The problem are the errors. If the query in that example was something like select * potato potato;, It'd still not yield the correct return value thus missing the error test.
I'm not particularly good with sqlplus nor ksh, probably just missing something obvious... Could someone lend me a hand here?
Thanks!
I believe $? is returning the exit status of the typeset command, not the sqlplus command.
It may be easier to output the results of your SQLPLUS statement to a file instead of into a variable. Then you could either read that file with grep, looking for an "ORA-" message, or check the exit status variable.
sqlplus -s $eSQLU/$eSQLP#$eSQLS > querylog.tmp <<EOF
set echo off newpage 0 space 0 pagesize 0 feed off head off verify off lines 999
WHENEVER SQLERROR EXIT 1
$eQUERY
exit;
EOF
echo $?

how to remove space in echo?

s#!/bin/ksh
usrid=`sql_login.sh`
if [ "$?" -ne "0" ]; then
echo "sql login failed-Username/Password not available in control file"
exit -1
fi
a=`sqlplus -s ${usrid} <<EOF
set pause off
set heading off
set feedback off
set serveroutput off;
select 10 from dual;
exit;
EOF`
b=`sqlplus -s ${usrid} <<EOF
set pause off
set heading off
set feedback off
select 11 from dual;
exit;
EOF`
echo "Out of sqlplus session";
echo $a$b;
hi its giving output like 10 11? i need it 1011 how can i achive this?
solution:
a1=$(echo ${a#}) ;
b1=$(echo ${b#}) ;
c1=$a1$b1;
echo $c1;
There must be spaces included in the value of a (and probably b too)
You can get rid of the space by modify the value of the variable.
assuming you have bash, ksh or other POSIX shell
echo ${a% }${b% }
will probably work.
${var% } says, for ${var} remove from the left of the variable's value a space char (if there is one there.
If for some reason, you find that the space is in the front, like ' a', then use
${a# }, which means remove from the right of the variable's value a space char.
EDIT : kurumi's solution (assuming bash and POSIX shell is probably better)
because then it doesn't matter where in the line that space value occurs.
BUT why don't you concatenate the two values and have just 1 sql query?
$ab=...
select 10 + 11 from dual;
You may have to consult with you oracle friends to get this exactly right, but in the SQL Sybase DB (and probably MS SQL), this wouldn't be a problem.d
ALSO, Stop using backquotes, they are deprecate since 1992 (at least)
ab=$( cmd ) is so much nicer ;-)
I hope this helps.
P.S. as you appear to be a new user, if you get an answer that helps you please remember to mark it as accepted, or give it a + (or -) as a useful answer
you can try
c="$a$b"
echo ${c// }
or remove spaces at each individual variables themselves
a=${a// }
b=${b// }
or you can call externals
echo $a | sed -e 's/^[ \t]*//' -e 's/[ \t]*$//'

Putting the output of a command with interaction inside a variable while using grep in bash

This program I use has it's own variables to set when you run it, so I want to set those variables and then greping the output then storing it inside a variable. However, I don't know how to go about this the correct way. The idea I have doesn't work. The focus is on lines 7 through 14.
1 #!/usr/local/bin/bash
2 source /home/gempak/NAWIPS/Gemenviron.profile
3 FILENAME="$(date -u '+%Y%m%d')_sao.gem"
4 SFFILE="$GEMDATA/surface/$FILENAME"
5 echo -n "Enter the station ID: "
6 read -e STATION
7 OUTPUT=$(sflist << EOF
8 SFFILE = $SFFILE
9 AREA = #$STATION
10 DATTIM = all
11 SFPARM = TMPF;DWPF
12 run
13 exit
14 EOF)
15 echo $OUTPUT
But I get this:
./listweather: line 7: unexpected EOF while looking for matching `)'
./listweather: line 16: syntax error: unexpected end of file
Putting together everyone's answers, I came across a working solution myself. This code works for me:
#!/usr/local/bin/bash
source /home/gempak/NAWIPS/Gemenviron.profile
FILENAME="$(date -u '+%Y%m%d')_sao.gem"
SFFILE="$GEMDATA/surface/$FILENAME"
echo -n "Enter the station ID: "
read -e STATION
OUTPUT=$(sflist << EOF
SFFILE = $SFFILE
AREA = #$STATION
DATTIM = ALL
SFPARM = TMPF;DWPF
run
exit
EOF
)
echo $OUTPUT | grep $STATION
Thanks everyone!
I'd put your program to run in a separate .sh script file, and then run the script from your first file, passing the arguments you want to pass as command line arguments. That way you can test them separately.
You could also do it in a function, but I like the modularity of the second script. I don't udnerstand exactly what you are trying to do above, but something like:
runsflist.sh:
#!/bin/bash
FILENAME="$(date -u '+%Y%m%d')_sao.gem"
SFFILE="$GEMDATA/surface/$FILENAME"
AREA = #$STATION
DATTIM = all
SFPARM = TMPF;DWPF
grep $STATION | sflist
main.sh:
#!/bin/bash
echo -n "Enter the station ID: "
read -e STATION
OUTPUT=`runsflist.sh`
echo $OUTPUT
If sflist needs interaction, I'd try something like this:
SFFILE=$(
( echo SFFILE = "$SFFILE"
echo AREA = "#$STATION"
echo DATTIM = all
echo SFPARM = TMPF;DWPF
echo run
cat
) | sflist)
Unfortunately, you have to type exit as part of the interaction.

Resources