173

I was wondering if anyone would be able to tell me about whether it is possible to use shell to check if a PostgreSQL database exists?

I am making a shell script and I only want it to create the database if it doesn't already exist but up to now haven't been able to see how to implement it.

wonea
  • 4,783
  • 17
  • 86
  • 139
Jimmy
  • 12,087
  • 28
  • 102
  • 192

15 Answers15

236

Note/Update (2021): While this answer works, philosophically I agree with other comments that the right way to do this is to ask Postgres.

Check whether the other answers that have psql -c or --command in them are a better fit for your use case (e.g. Nicholas Grilly's, Nathan Osman's, bruce's or Pedro's variant


I use the following modification of Arturo's solution:

psql -lqt | cut -d \| -f 1 | grep -qw <db_name>


What it does

psql -l outputs something like the following:

                                        List of databases
     Name  |   Owner   | Encoding |  Collate   |   Ctype    |   Access privileges   
-----------+-----------+----------+------------+------------+-----------------------
 my_db     | my_user   | UTF8     | en_US.UTF8 | en_US.UTF8 | 
 postgres  | postgres  | LATIN1   | en_US      | en_US      | 
 template0 | postgres  | LATIN1   | en_US      | en_US      | =c/postgres          +
           |           |          |            |            | postgres=CTc/postgres
 template1 | postgres  | LATIN1   | en_US      | en_US      | =c/postgres          +
           |           |          |            |            | postgres=CTc/postgres
(4 rows)

Using the naive approach means that searching for a database called "List, "Access" or "rows" will succeed. So we pipe this output through a bunch of built-in command line tools to only search in the first column.


The -t flag removes headers and footers:

 my_db     | my_user   | UTF8     | en_US.UTF8 | en_US.UTF8 | 
 postgres  | postgres  | LATIN1   | en_US      | en_US      | 
 template0 | postgres  | LATIN1   | en_US      | en_US      | =c/postgres          +
           |           |          |            |            | postgres=CTc/postgres
 template1 | postgres  | LATIN1   | en_US      | en_US      | =c/postgres          +
           |           |          |            |            | postgres=CTc/postgres

The next bit, cut -d \| -f 1 splits the output by the vertical pipe | character (escaped from the shell with a backslash), and selects field 1. This leaves:

 my_db             
 postgres          
 template0         
                   
 template1         
         

grep -w matches whole words, and so won't match if you are searching for temp in this scenario. The -q option suppresses any output written to the screen, so if you want to run this interactively at a command prompt you may with to exclude the -q so something gets displayed immediately.

Note that grep -w matches alphanumeric, digits and the underscore, which is exactly the set of characters allowed in unquoted database names in postgresql (hyphens are not legal in unquoted identifiers). If you are using other characters, grep -w won't work for you.


The exit status of this whole pipeline will be 0 (success) if the database exists or 1 (failure) if it doesn't. Your shell will set the special variable $? to the exit status of the last command. You can also test the status directly in a conditional:

if psql -lqt | cut -d \| -f 1 | grep -qw <db_name>; then
    # database exists
    # $? is 0
else
    # ruh-roh
    # $? is 1
fi
kibibu
  • 6,115
  • 1
  • 35
  • 41
  • 8
    You can also add `... | grep 0` to make the shell return value be 0 if the DB does not exist and 1 if it does; or `... | grep 1` for the opposite behavior – acjay Jun 20 '13 at 05:49
  • 2
    @acjohnson55 even better: drop the `wc` entirely. See my revision. (If you want to reverse the exit status, Bash supports a bang operator: `! psql ...`) – benesch Jan 19 '14 at 02:30
  • 1
    Further to other suggesting to drop the `wc` command, I would use `grep -qw `. This will cause the shell to return `0` if there's a match and `1` otherwise. Then, `$?` will contain the return value and you can use that to decide what to do next. So, I recommend not using `wc` in this case. `grep` will do what you need. – Matt Friedman Dec 29 '14 at 16:12
  • I got around to updating this answer based on your feedback. Thanks all. – kibibu Mar 04 '16 at 02:47
  • Thanks phils, added a note about that failure case – kibibu Mar 15 '16 at 20:48
  • `psql -lqt | cut -d \| -f 1 | sed -E -e 's/^\s+//' -e 's/\s+$//' -e '/^$/d' | egrep -q '^DB$'` – x-yuri Nov 26 '18 at 23:17
  • @x-yuri can you explain what you're doing with sed here? – kibibu Nov 30 '18 at 08:53
  • @kibibu I remove leading (`-e 's/^\s+//'`), trailing spaces (`-e 's/\s+$//'`), and empty lines (`-e '/^$/d'`). `-E` makes it use extended regular expressions. One can't use `psql -A` here, since it's not one database per line. – x-yuri Nov 30 '18 at 14:54
132

The following shell code seems to work for me:

if [ "$( psql -XtAc "SELECT 1 FROM pg_database WHERE datname='DB_NAME'" )" = '1' ]
then
    echo "Database already exists"
else
    echo "Database does not exist"
fi

Quick help about the psql flags given above:

General options:
  -c, --command=COMMAND    run only single command (SQL or internal) and exit
  -X, --no-psqlrc          do not read startup file (~/.psqlrc)

Output format options:
  -A, --no-align           unaligned table output mode
  -t, --tuples-only        print rows only
yolenoyer
  • 8,797
  • 2
  • 27
  • 61
Nathan Osman
  • 71,149
  • 71
  • 256
  • 361
  • 4
    I like that you don't relay on any external cut grep wc and stuff.. you check for db existence, which supposingly means you have at least psql, ant thats the least and only command you use! very nice indeed. Besides the subject didn't mention the shell type nor the commands version or distro.. I'd never relay on such a pletora of pipes to system tooling I've seen on the other answers for knowing that. It leads to years-later-issues – Riccardo Manfrin Oct 09 '19 at 08:17
  • 1
    I agree with @RiccardoManfrin this seems like the more direct solution. – Travis Nov 15 '19 at 17:07
  • 1
    If you need to perform this with non postgres user you can add -U user, but have to list a database to connect to, as none could exist you can use the postgres template1 database that always exists: `psql -U user -tAc "SELECT 1 FROM pg_database WHERE datname='DB_NAME'" template1` – jan Jul 10 '20 at 09:24
  • 1
    In cygwin psql adds strange control characters to the output ('1\C-M') and one needs to check if the output only starts with 1: `if [[ $(...) == 1* ]]` – jan Jul 10 '20 at 09:53
30
postgres@desktop:~$ psql -l | grep <exact_dbname> | wc -l

This will return 1 if the database specified exists or 0 otherwise.

Also, if you try to create a database that already exists, postgresql will return an error message like this:

postgres@desktop:~$ createdb template1
createdb: database creation failed: ERROR:  database "template1" already exists
wonea
  • 4,783
  • 17
  • 86
  • 139
Arturo
  • 713
  • 7
  • 14
  • 10
    The first suggestion is very dangerous. What would happen of `exact_dbname_test` would exist ? The only way of testing is trying to connect to it. – wildplasser Jan 27 '13 at 16:31
  • 6
    This answer is not robust! It prints (not returns!) nonzero numbers if your search term appears in another column. Please see kibibu's answer for a more correct way to do this. – acjay Jun 20 '13 at 05:46
  • 1
    "grep -w foo" can give you false positives when a database named "foo-bar" exists. Not to mention it will find all the words in the psql output header. – Marius Gedminas Sep 30 '13 at 11:45
  • 1
    i strongly disagree with this answer. It will ALWAYS be true if you use this expression in a logical statement. you can try these examples to test: `psql -l | grep doesnt_matter_what_you_grep | wc -l && echo "true"` vs `psql -l | grep it_does_matter_here && echo "only true if grep returns anything"` – Mike Lyons Nov 13 '13 at 17:29
  • @wildplasser if that's a worry `grep -E "\bexact_dbname\b"` (or even `-E " exact_dbname "`) the point is you can customize the matching expression to suit your needs and use `wc` to return the word count. But this should be combined with the cut that kibibu suggested (`psql -lt | cut -d \| -f 1 | grep " dbname " | wc -l`) – vol7ron Feb 03 '14 at 04:04
  • 2
    What's with all the cutting? If you want to make sure that you're only looking at the first column, just put it in the regex: `psql -l | grep '^ exact_dbname\b'`, which sets an exit code if not found. – Steve Bennett Feb 13 '14 at 12:09
  • *No* answer depending on `grep -w` or using `\b` as a delimiter is correct, for the reason already indicated by Marius Gedminas -- database names can contain non-word-constituent characters like `-`, and therefore *any* word-delimited search for `foo` would still match `foo-bar`. – phils Apr 21 '16 at 22:33
29

I'm new to postgresql, but the following command is what I used to check if a database exists

if psql ${DB_NAME} -c '\q' 2>&1; then
   echo "database ${DB_NAME} exists"
fi
bruce
  • 425
  • 3
  • 3
12

You can create a database, if it doesn't already exist, using this method:

if [[ -z `psql -Atqc '\list mydatabase' postgres` ]]; then createdb mydatabase; fi
Nicolas Grilly
  • 133
  • 1
  • 4
10

I'm combining the other answers to a succinct and POSIX compatible form:

psql -lqtA | grep -q "^$DB_NAME|"

A return of true (0) means it exists.

If you suspect your database name might have a non-standard character such as $, you need a slightly longer approach:

psql -lqtA | cut -d\| -f1 | grep -qxF "$DB_NAME"

The -t and -A options make sure the output is raw and not "tabular" or whitespace-padded output. Columns are separated by the pipe character |, so either the cut or the grep has to recognize this. The first column contains the database name.

EDIT: grep with -x to prevent partial name matches.

Otheus
  • 785
  • 10
  • 18
6
#!/bin/sh
DB_NAME=hahahahahahaha
psql -U postgres ${DB_NAME} --command="SELECT version();" >/dev/null 2>&1
RESULT=$?
echo DATABASE=${DB_NAME} RESULT=${RESULT}
#
wildplasser
  • 43,142
  • 8
  • 66
  • 109
  • +1 For causal sporadic use, I'd opt for the other answer, but for a routine script, this is more clean and robust. Caveat: check that the user 'postgres' can cannect without password. – leonbloy Jan 27 '13 at 20:11
  • Yes there is a problem about the username being needed. OTOH: you wouldn't want to use an other role having no connect permission. – wildplasser Jan 27 '13 at 21:51
4

For completeness, another version using regex rather than string cutting:

psql -l | grep '^ exact_dbname\b'

So for instance:

if psql -l | grep '^ mydatabase\b' > /dev/null ; then
  echo "Database exists already."
  exit
fi
Steve Bennett
  • 114,604
  • 39
  • 168
  • 219
  • Using `\b` has the same problem as all the answers using `grep -w` which is that database names can contain non-word-constituent characters like `-` and therefore attempts to match `foo` will also match `foo-bar`. – phils Apr 21 '16 at 22:23
3

The other solutions (which are fantastic) miss the fact that psql can wait a minute or more before timing out if it can't connect to a host. So, I like this solution, which sets the timeout to 3 seconds:

PGCONNECT_TIMEOUT=3 psql development -h db -U postgres -c ""

This is for connecting to a development database on the official postgres Alpine Docker image.

Separately, if you're using Rails and want to setup a database if it doesn't already exist (as when launching a Docker container), this works well, as migrations are idempotent:

bundle exec rake db:migrate 2>/dev/null || bundle exec rake db:setup
Dan Kohn
  • 33,811
  • 9
  • 84
  • 100
2

kibibu's accepted answer is flawed in that grep -w will match any name containing the specified pattern as a word component.

i.e. If you look for "foo" then "foo-backup" is a match.

Otheus's answer provides some good improvements, and the short version will work correctly for most cases, but the longer of the two variants offered exhibits a similar problem with matching substrings.

To resolve this issue, we can use the POSIX -x argument to match only entire lines of the text.

Building on Otheus's answer, the new version looks like this:

psql -U "$USER" -lqtA | cut -d\| -f1 | grep -qFx "$DBNAME"

That all said, I'm inclined to say that Nicolas Grilly's answer -- where you actually ask postgres about the specific database -- is the best approach of all.

Community
  • 1
  • 1
phils
  • 71,335
  • 11
  • 153
  • 198
2

psql -l|awk '{print $1}'|grep -w <database>

shorter version

Justin
  • 48
  • 2
  • 8
1

I'm still pretty inexperienced with shell programming, so if this is really wrong for some reason, vote me down, but don't be too alarmed.

Building from kibibu's answer:

# If resulting string is not zero-length (not empty) then...
if [[ ! -z `psql -lqt | cut -d \| -f 1 | grep -w $DB_NAME` ]]; then
  echo "Database $DB_NAME exists."
else
  echo "No existing databases are named $DB_NAME."
fi
David Winiecki
  • 4,093
  • 2
  • 37
  • 39
1

This command will return the number of databases that are called DATABASE_NAME: psql -At -U postgres -c "select count(*) from pg_databases where datname = 'DATABASE_NAME';

So

if [ "$(psql -At -U postgres -c "select count(*) from pg_databases where datname = 'DATABASE_NAME`;")" -eq 0 ] ; then
   # This runs if the DB doesn't exist.
fi
Amandasaurus
  • 58,203
  • 71
  • 188
  • 248
0
  • In one line:

PGPASSWORD=mypassword psql -U postgres@hostname -h postgres.hostname.com -tAc 'select 1' -d dbnae || echo 0

This will return 1 if db exists 0 if not

  • or more readable:
if [ "$(PGPASSWORD=mypassword psql -U postgres@hostname -h postgres.hostname.com -tAc 'select 1' -d dbnae || echo 0 )" = '1' ]
then
    echo "Database already exists"
else
    echo "Database does not exist"
fi
abahet
  • 10,355
  • 4
  • 32
  • 23
0

Trigger divide by zero if it doesn't exist then check return code like this:

sql="SELECT 1/count(*) FROM pg_database WHERE datname='db_name'";
error=$(psql -h host -U user -c "$sql" postgres);
if $error
then
  echo "doesn't exist";
else
  echo "exists";
fi
Aaron
  • 41
  • 1