Syntax error on $$ double dollars in Postgres function creation [duplicate] - bash

This question already has answers here:
How to cat <<EOF >> a file containing code?
(5 answers)
Closed 3 years ago.
I am attempting to create a simple function during the startup of a postgres instance. I am doing this by mapping some .sh files to the /docker-entrypoint-initdb.d dir.
I continuously hit a problem with this simple function.
#!/bin/bash
set -e
psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" --dbname "$POSTGRES_DB" <<-EOSQL
CREATE OR REPLACE FUNCTION hi() RETURNS TEXT AS $$
BEGIN
RETURN 'hi'
END;
$$;
EOSQL
2020-01-29 05:12:30.817 UTC [62] ERROR: syntax error at or near "1" at character 49
2020-01-29 05:12:30.817 UTC [62] STATEMENT: CREATE OR REPLACE FUNCTION hi() RETURNS TEXT AS 1
BEGIN
RETURN 'hi'
END;
ERROR: syntax error at or near "1"
LINE 1: CREATE OR REPLACE FUNCTION hi() RETURNS TEXT AS 1
If I change it to something with actual content:
#!/bin/bash
set -e
psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" --dbname "$POSTGRES_DB" <<-EOSQL
CREATE OR REPLACE FUNCTION hi() RETURNS TEXT AS $func$
BEGIN
RETURN 'hi'
END;
$func$;
EOSQL
I get another error at the same place:
2020-01-29 05:17:36.161 UTC [62] ERROR: syntax error at or near "$" at character 49
2020-01-29 05:17:36.161 UTC [62] STATEMENT: CREATE OR REPLACE FUNCTION hi() RETURNS TEXT AS $
BEGIN
RETURN 'hi'
END;
ERROR: syntax error at or near "$"
LINE 1: CREATE OR REPLACE FUNCTION hi() RETURNS TEXT AS $
Running with the latest postgres version: 12.1
What precisely is the problem with this function definition and why am I getting this error?

The part of your script from <<-EOSQL to EOSQL is called a "here document", and that functionality is documented at the Bash Reference Manual, §3.6.6 "Here Documents". Per that section:
If any part of word [in your case EOSQL] is quoted, […] the lines in the here-document are not expanded. If word is unquoted, all lines of the here-document are subjected to parameter expansion, command substitution, and arithmetic expansion, […]
In other words — because you haven't quoted any part of EOSQL, Bash is performing parameter expansion (and other similar substitutions) on the contents of the here-document, which includes replacing $$ with the process-ID, and $func with the empty string, before PostgreSQL sees them.
If you just change <<-EOSQL to <<-'EOSQL', it won't do that.

$func and $$ are variables in bash. The first one is the process ID of the shell instance, which explains the number in your error output; the second is just a normal user-defined variable, presumably empty, so $test$ becomes just $ (as $test gets replaced by an empty string).
You can either escape the dollar sign that bash finds significant, using \$\$ and \$test\$, or you can use <<-'EOSQL' to use single-quote (no variable substitution) semantics on the heredoc.
None of this has to do with PostgreSQL, it's bash doing what it thinks you want.

Related

unexpected EOF while looking for matching `"' unexpected end of file

I have a string
str="xx:mvt="this is the value""
While executing above string in a command( command $str ). Command is not accepting 'this is the value' as one value.
How to do this?
I wanted to replace ' character with \" to interpret special character. below is the code but getting above exception:
str=`sed "s/\"/\\\"/g" <<< "$str"`
The assign should be in form:
str='xx:mvt="this is the value"'
This will keep double quotes and assign string xx:mvt="this is the value" to the variable
Also the command (with sed) must be like:
str=`sed 's/\"/\\\"/g' <<< "$str"`

Bash: escaping variables

I'm writing a bash one liner.
This works (prints date to the console or tries to execute it):
-bash-4.1$ DATE=`$(date --date="2 days ago" +%F)` echo "${DATE}"
But this:
`DATE=$(date --date="2 days ago" +%F)` psql -d some_db -c "select row from table where started >= '${DATE}' and started < ('${DATE}'::date + '1 day'::interval);"
Gives:
ERROR: invalid input syntax for type timestamp: ""
LINE 1: ... table where started >= '' ...
Which means that ${DATE} is empty there.
What should I modify to make it work?
The first command is broken for a couple of reasons. Firstly, you are mixing backticks and $() in such a way that you are executing the output of the date command as if it were the name of another command.
To save the output of a command to a variable, use this syntax:
output=$(command)
Secondly, the variable would be expanded by the shell before it had been assigned a value, so you need to split up the command into two statements:
date=$(date_command); psql -c "select ... '$date'"
...or use a command substitution directly:
psql -c "select ... '$(date_command)"
Why do the date math in bash? PG can do it directly:
SELECT ... WHERE started >= (datefield - interval '2 day')
^^^^^^^^^^^^^^^^^

Shell - what does <<-'EOF' mean [duplicate]

I needed to write a script to enter multi-line input to a program (psql).
After a bit of googling, I found the following syntax works:
cat << EOF | psql ---params
BEGIN;
`pg_dump ----something`
update table .... statement ...;
END;
EOF
This correctly constructs the multi-line string (from BEGIN; to END;, inclusive) and pipes it as an input to psql.
But I have no idea how/why it works, can some one please explain?
I'm referring mainly to cat << EOF, I know > outputs to a file, >> appends to a file, < reads input from file.
What does << exactly do?
And is there a man page for it?
The cat <<EOF syntax is very useful when working with multi-line text in Bash, eg. when assigning multi-line string to a shell variable, file or a pipe.
Examples of cat <<EOF syntax usage in Bash:
1. Assign multi-line string to a shell variable
$ sql=$(cat <<EOF
SELECT foo, bar FROM db
WHERE foo='baz'
EOF
)
The $sql variable now holds the new-line characters too. You can verify with echo -e "$sql".
2. Pass multi-line string to a file in Bash
$ cat <<EOF > print.sh
#!/bin/bash
echo \$PWD
echo $PWD
EOF
The print.sh file now contains:
#!/bin/bash
echo $PWD
echo /home/user
3. Pass multi-line string to a pipe in Bash
$ cat <<EOF | grep 'b' | tee b.txt
foo
bar
baz
EOF
The b.txt file contains bar and baz lines. The same output is printed to stdout.
This is called heredoc format to provide a string into stdin. See https://en.wikipedia.org/wiki/Here_document#Unix_shells for more details.
From man bash:
Here Documents
This type of redirection instructs the shell to read input from
the current source until a line
containing only word (with no trailing
blanks) is seen.
All of the lines read up to that point are then used as the
standard input for a command.
The format of here-documents is:
<<[-]word
here-document
delimiter
No parameter expansion, command substitution, arithmetic expansion, or
pathname expansion is performed on
word. If any characters in word are
quoted, the
delimiter is the result of quote removal on word, and the lines
in the here-document are not expanded.
If word is unquoted, all lines of the
here-document are subjected to parameter expansion, command
substitution, and arithmetic
expansion. In the latter case, the
character sequence \<newline> is
ignored, and \ must be used to quote the characters \, $, and `.
If the redirection operator is <<-, then all leading tab characters
are stripped from input lines and the
line containing delimiter. This
allows here-documents within shell scripts to be indented in a natural fashion.
In your case, "EOF" is known as a "Here Tag". Basically <<Here tells the shell that you are going to enter a multiline string until the "tag" Here. You can name this tag as you want, it's often EOF or STOP.
Some rules about the Here tags:
The tag can be any string, uppercase or lowercase, though most people use uppercase by convention.
The tag will not be considered as a Here tag if there are other words in that line. In this case, it will merely be considered part of the string. The tag should be by itself on a separate line, to be considered a tag.
The tag should have no leading or trailing spaces in that line to be considered a tag. Otherwise it will be considered as part of the string.
example:
$ cat >> test <<HERE
> Hello world HERE <-- Not by itself on a separate line -> not considered end of string
> This is a test
> HERE <-- Leading space, so not considered end of string
> and a new line
> HERE <-- Now we have the end of the string
POSIX 7
kennytm quoted man bash, but most of that is also POSIX 7: http://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_07_04 :
The redirection operators "<<" and "<<-" both allow redirection of lines contained in a shell input file, known as a "here-document", to the input of a command.
The here-document shall be treated as a single word that begins after the next <newline> and continues until there is a line containing only the delimiter and a <newline>, with no <blank> characters in between. Then the next here-document starts, if there is one. The format is as follows:
[n]<<word
here-document
delimiter
where the optional n represents the file descriptor number. If the number is omitted, the here-document refers to standard input (file descriptor 0).
If any character in word is quoted, the delimiter shall be formed by performing quote removal on word, and the here-document lines shall not be expanded. Otherwise, the delimiter shall be the word itself.
If no characters in word are quoted, all lines of the here-document shall be expanded for parameter expansion, command substitution, and arithmetic expansion. In this case, the <backslash> in the input behaves as the <backslash> inside double-quotes (see Double-Quotes). However, the double-quote character ( '"' ) shall not be treated specially within a here-document, except when the double-quote appears within "$()", "``", or "${}".
If the redirection symbol is "<<-", all leading <tab> characters shall be stripped from input lines and the line containing the trailing delimiter. If more than one "<<" or "<<-" operator is specified on a line, the here-document associated with the first operator shall be supplied first by the application and shall be read first by the shell.
When a here-document is read from a terminal device and the shell is interactive, it shall write the contents of the variable PS2, processed as described in Shell Variables, to standard error before reading each line of input until the delimiter has been recognized.
Examples
Some examples not yet given.
Quotes prevent parameter expansion
Without quotes:
a=0
cat <<EOF
$a
EOF
Output:
0
With quotes:
a=0
cat <<'EOF'
$a
EOF
or (ugly but valid):
a=0
cat <<E"O"F
$a
EOF
Outputs:
$a
Hyphen removes leading tabs
Without hyphen:
cat <<EOF
<tab>a
EOF
where <tab> is a literal tab, and can be inserted with Ctrl + V <tab>
Output:
<tab>a
With hyphen:
cat <<-EOF
<tab>a
<tab>EOF
Output:
a
This exists of course so that you can indent your cat like the surrounding code, which is easier to read and maintain. E.g.:
if true; then
cat <<-EOF
a
EOF
fi
Unfortunately, this does not work for space characters: POSIX favored tab indentation here. Yikes.
Using tee instead of cat
Not exactly as an answer to the original question, but I wanted to share this anyway: I had the need to create a config file in a directory that required root rights.
The following does not work for that case:
$ sudo cat <<EOF >/etc/somedir/foo.conf
# my config file
foo=bar
EOF
because the redirection is handled outside of the sudo context.
I ended up using this instead:
$ sudo tee <<EOF /etc/somedir/foo.conf >/dev/null
# my config file
foo=bar
EOF
<< EoF basically means:
<< - "read the multi-line input that begins from the next line onward, and treat it as if it's code in a separate file"
EoF - "stop reading immediately after the word EoF is found in the multi-line input"
As other answers have explained, the multi-line input is called a Here Document
A Here Document is often used to generate output to be passed to a subsequent process. For example cat << EoF can be used to generate a desired output, using a Here Document.
Here's an example of using a Here Document to create a text document on the fly:
cat << EoF > ./my-document.txt
Hello world
Have a nice day
EoF
A little extension to the above answers. The trailing > directs the input into the file, overwriting existing content. However, one particularly convenient use is the double arrow >> that appends, adding your new content to the end of the file, as in:
cat <<EOF >> /etc/fstab
data_server:/var/sharedServer/authority/cert /var/sharedFolder/sometin/authority/cert nfs
data_server:/var/sharedServer/cert /var/sharedFolder/sometin/vsdc/cert nfs
EOF
This extends your fstab without you having to worry about accidentally modifying any of its contents.
note to mention that cat << \EOT (see the backslash) will not expand any variables inside, while cat << EOT will do.
examples:
FOO="bar"
cat << \EOT > foobar.txt
echo "$FOO"
EOT
will output:
echo $FOO
while:
FOO="bar"
cat << EOT > foobar.txt
echo "$FOO"
EOT
will output:
echo "bar"
Example to create a json file:
cat << EoF > ./allaccess.json
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"s3:*"
],
"Resource": [
"arn:aws:s3:::*"
]
}
]
}
EoF
As a result:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"s3:*"
],
"Resource": [
"arn:aws:s3:::*"
]
}
]
}
Long story short, EOF marker(but a different literal can be used as well) is a heredoc format that allows you to provide your input as multiline.
A lot of confusion comes from how cat actually works it seems.
You can use cat with >> or > as follows:
$ cat >> temp.txt
line 1
line 2
While cat can be used this way when writing manually into console, it's not convenient if I want to provide the input in a more declarative way so that it can be reused by tools and also to keep indentations, whitespaces, etc.
Heredoc allows to define your entire input as if you are not working with stdin but typing in a separate text editor. This is what Wikipedia article means by:
it is a section of a source code file that is treated as if it were a
separate file.
This isn't necessarily an answer to the original question, but a sharing of some results from my own testing. This:
<<test > print.sh
#!/bin/bash
echo \$PWD
echo $PWD
test
will produce the same file as:
cat <<test > print.sh
#!/bin/bash
echo \$PWD
echo $PWD
test
So, I don't see the point of using the cat command.
Worth noting that here docs work in bash loops too.
This example shows how-to get the column list of table:
export postgres_db_name='my_db'
export table_name='my_table_name'
# start copy
while read -r c; do test -z "$c" || echo $table_name.$c , ; done < <(cat << EOF | psql -t -q -d $postgres_db_name -v table_name="${table_name:-}"
SELECT column_name
FROM information_schema.columns
WHERE 1=1
AND table_schema = 'public'
AND table_name =:'table_name' ;
EOF
)
# stop copy , now paste straight into the bash shell ...
output:
my_table_name.guid ,
my_table_name.id ,
my_table_name.level ,
my_table_name.seq ,
or even without the new line
while read -r c; do test -z "$c" || echo $table_name.$c , | perl -ne
's/\n//gm;print' ; done < <(cat << EOF | psql -t -q -d $postgres_db_name -v table_name="${table_name:-}"
SELECT column_name
FROM information_schema.columns
WHERE 1=1
AND table_schema = 'public'
AND table_name =:'table_name' ;
EOF
)
# output: daily_issues.guid ,daily_issues.id ,daily_issues.level ,daily_issues.seq ,daily_issues.prio ,daily_issues.weight ,daily_issues.status ,daily_issues.category ,daily_issues.name ,daily_issues.description ,daily_issues.type ,daily_issues.owner

Error While inserting username and pass word into database

I am trying to insert following data in psql and its showing me weird error.
-bash-4.1$ psql -t -d mydb -c 'insert into ftp_mgr (id, info) values("192.168.1.12", '{"username": "Administrator", "password": "abc456$", "serverAddr":"192.168.1.12"}');'
psql: warning: extra command-line argument "password:" ignored
psql: warning: extra command-line argument "abc456$," ignored
psql: warning: extra command-line argument "serverAddr:192.168.1.12});" ignored
psql: FATAL: role "Administrator," does not exist
This is nothing to do with PostgreSQL. It's a shell quoting problem.
You cannot nest single quotes in bash. So this:
'insert into ftp_mgr (id, info) values("192.168.1.12", '{"username": "Administrator", "password": "abc456$", "serverAddr":"192.168.1.12"}');'
is read up to:
'insert ...'{
and the { is *not part of the quoted string. So the shell tries to intepret {"username": "Administrator", "password": "abc456$", "serverAddr":"192.168.1.12"} as a shell command.
The simplest thing to do here is to use a quoted here-document to avoid the need to deal with ' literal quoting mixing with the shell's quoting:
psql -t -d mydb <<'__END__'
insert into ftp_mgr (id, info) values
("192.168.1.12", '{"username": "Administrator", "password": "abc456$", "serverAddr":"192.168.1.12"}');
__END__
The quotes around the here-document tag are important. If you leave them out then bash still looks for and replaces $variable strings, etc, within the here-document text. That can be really handy, but it's clearly not what you want in this case.
if you must do it with -c you have a couple of options:
Use the shell's string concatenation. Whenever you want a single-quote, end the current single quoted string, write "'", and open a new single quoted string. So you'd write:
'insert ... values(..., '"'"'{"....
Confusing to read, isn't it? The first ' ends the single-quoted shell string. Then "'" is a single quote, quoted by double quotes that're consumed by the shell. Then the next ' begins a new single-quoted string.
That's horrible to read, and it's not much better written as:
'insert ... values(..., '\''{"....
so personally, when I need single quotes within an argument string I either double-quote the whole string and backslash-escape shell metacharacters:
"insert into ftp_mgr (id, info) values(\"192.168.1.12\", '{\"username\": \"Administrator\", \"password\": \"abc456\$\", \"serverAddr\":\"192.168.1.12\"}');"
... or where possible I use a quoted here-document to get around the whole horrifying mess.
My preference is to just avoid the shell for this kind of thing and use a simple Python script.
import psycopg2
conn = psycopg2.connect('dbname=postgres')
curs = conn.cursor()
curs.execute(r'insert into ftp_mgr (id, info) values(%s, %s);', (
r'192.168.1.12',
r'{"username": "Administrator", "password": "abc456$", "serverAddr":"192.168.1.12"}'
))
Python's support for raw-strings (r''), triple quote strings ("""), etc make this sort of thing much easier.

Line error in shell script

I have the following code in a shell script.
This only seems to work when it is not defined in a function.
The problematic line is the one containing the "<<".
The error message is
"./run: line 210: syntax error:
unexpected end of file"
How can I write this correctly within a function?
init_database()
{
cd ../cfg
db.sh << ENDC
$DB_ADMIN
0
y
n
ENDC
check_status
sqlplus $DB_SCHEMA#$DB_NAME < initial_data.sql
cd -
}
There are a number of ways to fix that problem.
1/ Unindent the here document end marker, such as:
cat <<EOF
hello
$PWD
EOF
but that will make your code look ugly.
2/ "Indent" the here document begin marker:
cat <<' EOF'
hello
$PWD
EOF
where that bit before the first EOF is exactly the same as the before the second (tab, four spaces, two tabs, whatever). This allows you to keep your nice indenting, although it doesn't expand variables inside the here-document ($PWD doesn't change).
3/ Allow tabs to be stripped from the start of input lines and the end marker.
cat <<-EOF
hello
$PWD
EOF
but there's no way to get tabs into the beginnings of lines.
4/ For your purposes, you can also use:
( echo "$DB_ADMIN";
echo "" ;
echo "0" ;
echo "y" ;
echo "n"
) | db.sh
check_status
sqlplus $DB_SCHEMA#$DB_NAME < initial_data.sql
cd -
I believe number 4 is the best option for you. It allows nice lining up of the input, tabs and spaces anywhere in the lines and variable expansion.
The end of your "Here document" needs to be unindented, I'm afraid.
The ENDC label must be alone in a line without leading/trailing whitspaces.

Resources