使用Shell检查PostgreSQL中是否存在数据库

175
我想知道是否可以使用shell脚本来检查PostgreSQL数据库是否存在? 我正在编写一个shell脚本,如果数据库不存在,我只想创建它,但目前还没有找到如何实现它的方法。
15个回答

238

注/更新(2021年):虽然这个答案可行,但从哲学上讲,我同意其他评论者的观点,正确的方法是询问Postgres

检查是否有使用psql -c--command的其他答案更适合您的用例(例如Nicholas Grilly'sNathan Osman'sbruce'sPedro's variant


我使用Arturo的解决方案的以下修改:

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


它是如何工作的

psql -l输出类似以下内容:

                                        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)

使用朴素方法意味着搜索名为 "List"、"Access" 或 "rows" 的数据库将成功。因此,我们通过一系列内置命令行工具对输出进行管道处理,只搜索第一列。
-t 标志会删除头部和页脚:
 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

下一步,cut -d \| -f 1 通过竖杠|字符(用反斜杠从shell中转义)分割输出,并选择第1个字段。这将留下以下内容:
 my_db             
 postgres          
 template0         
                   
 template1         
         

grep -w可以匹配整个单词,因此在搜索temp时不会匹配。选项-q抑制了写入屏幕的任何输出,因此如果您想在命令提示符下交互式运行此命令,您可能希望排除-q以便立即显示某些内容。

请注意,grep -w可以匹配字母数字、数字和下划线,这正是postgresql中未引用数据库名称允许使用的字符集(连字符在未引用的标识符中不合法)。如果您使用其他字符,则grep -w无法为您工作。


如果数据库存在,则整个管道的退出状态将为0(成功),否则为1(失败)。您的shell将设置特殊变量$?为最后一个命令的退出状态。您还可以直接在条件语句中测试状态:

if psql -lqt | cut -d \| -f 1 | grep -qw <db_name>; then
    # database exists
    # $? is 0
else
    # ruh-roh
    # $? is 1
fi

9
您可以加上... | grep 0,如果数据库不存在,则使shell返回值为0,如果存在则返回1;或者使用... | grep 1来实现相反的行为。 - acjay
2
@acjohnson55 更好的做法是:完全删除 wc。请看我的修改。(如果你想要反转退出状态,Bash 支持一个感叹号操作符:! psql ... - benesch
1
除了其他人建议放弃wc命令外,我会使用grep -qw <term>。这将导致Shell在匹配成功时返回0,否则返回1。然后,$?将包含返回值,您可以使用它来决定下一步该做什么。因此,我建议在这种情况下不要使用wcgrep将满足您的需求。 - Matt Friedman
我根据你们的反馈更新了这个答案。谢谢大家。 - kibibu
谢谢Phils,已经添加了一个关于失败情况的注释。 - kibibu
显示剩余3条评论

134
以下 shell 代码对我来说似乎有效:
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

关于上面给出的psql标志的快速帮助:

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

4
我很喜欢你不依赖于任何外部 cut grep wc 等工具。你检查数据库的存在,这意味着你至少有 psql,而这是你唯一使用的命令!非常好。此外,主题没有提及 shell 类型、命令版本或发行版。我从未依赖过其他答案中看到的大量管道来进行系统工具的知识获取。这会导致多年后出现问题。 - Riccardo Manfrin
1
我同意@RiccardoManfrin的看法,这似乎是更直接的解决方案。 - Travis
1
如果您需要使用非postgres用户执行此操作,可以添加-U user,但必须列出要连接的数据库,因为可能不存在任何数据库,您可以使用始终存在的postgres template1数据库:psql -U user -tAc“SELECT 1 FROM pg_database WHERE datname ='DB_NAME'”template1 - jan
1
在Cygwin中,psql会向输出添加奇怪的控制字符('1\C-M'),因此需要检查输出是否仅以1开头: if [[ $(...) == 1* ]] - jan

30
postgres@desktop:~$ psql -l | grep <exact_dbname> | wc -l

如果指定的数据库存在,这将返回1;否则返回0。

此外,如果您尝试创建已经存在的数据库,PostgreSQL将返回如下错误消息:

postgres@desktop:~$ createdb template1
createdb: database creation failed: ERROR:  database "template1" already exists

10
第一个建议非常危险。如果exact_dbname_test存在会发生什么?唯一的测试方法是尝试连接它。 - wildplasser
6
这个答案不够健壮!如果你的搜索词出现在另一列中,它会打印(而不是返回)非零数字。请查看kibibu的答案,那里有更正确的方法来解决这个问题。 - acjay
1
当存在名为“foo-bar”的数据库时,“grep -w foo”可能会给出错误的结果。更不用说它会找到psql输出标题中的所有单词了。 - Marius Gedminas
1
我强烈反对这个答案。如果您在逻辑语句中使用此表达式,它将始终为真。您可以尝试以下示例进行测试:psql -l | grep doesnt_matter_what_you_grep | wc -l && echo "true"psql -l | grep it_does_matter_here && echo "only true if grep returns anything" - Mike Lyons
2
所有这些切割是怎么回事?如果你想确保只查看第一列,只需将其放入正则表达式中:psql -l | grep '^ exact_dbname\b',如果未找到,则设置退出代码。 - Steve Bennett
显示剩余2条评论

30

我刚接触postgresql,但以下命令是我用来检查数据库是否存在的

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

13
可以进一步简化为 psql ${DB_NAME} -c '' - Pedro Romano
2
看起来不错,但如果数据库存在但无法连接(可能是权限问题),它可能会出现假负面。 - Steve Bennett
12
如果你没有所需数据库的任何权限,那么对于你来说它是不存在的 :) - Viacheslav Dobromyslov

12

您可以使用以下方法创建数据库(如果它尚不存在):

if [[ -z `psql -Atqc '\list mydatabase' postgres` ]]; then createdb mydatabase; fi

10

我将其他答案结合起来,形成简洁且符合POSIX标准的形式:

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

返回true (0)表示它存在。

如果你怀疑你的数据库名称可能包含非标准字符,例如$,你需要稍微长一点的方法:

psql -lqtA | cut -d\| -f1 | grep -qxF "$DB_NAME"
-t-A 选项确保输出是原始的,而不是"表格"或空格填充的输出。列由管道字符 | 分隔,因此要么 cut 要么 grep 必须识别这个字符。第一列包含数据库名称。
编辑:使用带有 -x 的 grep 防止部分名称匹配。

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}
#

对于偶尔使用,我会选择另一个答案,但对于常规脚本,这更加清晰和健壮。注意:请检查用户“postgres”是否可以无需密码连接。 - leonbloy
是的,关于需要用户名的问题存在。另一方面,您不希望使用其他没有连接权限的角色。 - wildplasser

4

为了完整起见,以下是使用正则表达式而不是字符串截取的另一种版本:

psql -l | grep '^ exact_dbname\b'

所以举个例子:
if psql -l | grep '^ mydatabase\b' > /dev/null ; then
  echo "Database exists already."
  exit
fi

使用\b具有与所有使用grep -w的答案相同的问题,即数据库名称可能包含非单词成分字符,例如-,因此尝试匹配foo也会匹配foo-bar - phils

3
其他解决方案(非常棒)忽略了psql在无法连接到主机时可能会等待一分钟或更长时间才超时的事实。因此,我喜欢这个解决方案,它将超时设置为3秒钟:
PGCONNECT_TIMEOUT=3 psql development -h db -U postgres -c ""

这是用于连接到官方 postgres Alpine Docker 镜像上的开发数据库。
另外,如果您正在使用 Rails 并希望在不存在数据库时设置数据库(例如在启动 Docker 容器时),这很有效,因为迁移是幂等的。
bundle exec rake db:migrate 2>/dev/null || bundle exec rake db:setup

2

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

缩短版


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接