Verifique se o banco de dados existe no PostgreSQL usando shell

130

Eu queria saber se alguém poderia me dizer se é possível usar o shell para verificar se existe um banco de dados PostgreSQL?

Estou criando um script de shell e só quero que ele crie o banco de dados se ele ainda não existir, mas até agora não foi possível ver como implementá-lo.

Jimmy
fonte

Respostas:

199

Eu uso a seguinte modificação da solução do Arturo:

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


O que faz

psql -l gera algo como o seguinte:

                                        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)

Usar uma abordagem ingênua significa que a busca por um banco de dados chamado "Lista", "Acesso" ou "linhas" será bem-sucedida. Portanto, canalizamos essa saída através de várias ferramentas de linha de comando internas para pesquisar apenas na primeira coluna.


A -tbandeira remove cabeçalhos e rodapés:

 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

O próximo bit cut -d \| -f 1divide a saída pelo |caractere de tubo vertical (escapou do shell com uma barra invertida) e seleciona o campo 1. Isso deixa:

 my_db             
 postgres          
 template0         

 template1         

grep -wcorresponde a palavras inteiras e, portanto, não corresponderá se você estiver pesquisando tempnesse cenário. A -qopção suprime qualquer saída gravada na tela; portanto, se você deseja executá-lo interativamente em um prompt de comando, pode excluir o valor-q item para que algo seja exibido imediatamente.

Observe que grep -wcorresponde a alfanuméricos, dígitos e o sublinhado, que é exatamente o conjunto de caracteres permitido nos nomes de banco de dados não citados no postgresql (hífens não são legais em identificadores não citados). Se você estiver usando outros caracteres, grep -wnão funcionará para você.


O status de saída de todo esse pipeline será 0(êxito) se o banco de dados existir ou 1(falha) se não existir . Seu shell definirá a variável especial $?para o status de saída do último comando. Você também pode testar o status diretamente em uma condição:

if psql -lqt | cut -d \| -f 1 | grep -qw <db_name>; then
    # database exists
    # $? is 0
else
    # ruh-roh
    # $? is 1
fi
kibibu
fonte
8
Você também pode adicionar ... | grep 0para fazer com que o valor de retorno do shell seja 0 se o banco de dados não existir e 1 se existir; ou ... | grep 1para o comportamento oposto
acjay
2
@ acjohnson55 ainda melhor: solte o wcinteiramente. Veja minha revisão. (Se você quiser reverter o status de saída, Bash suporta um operador de estrondo: ! psql ...)
Benesch
Só agora vendo isso, agradável
vol7ron
1
Além de outras sugestões para descartar o wccomando, eu usaria grep -qw <term>. Isso fará com que o shell retorne 0se houver uma correspondência ou 1não. Em seguida, $?conterá o valor de retorno e você poderá usá-lo para decidir o que fazer a seguir. Portanto, eu recomendo não usar wcneste caso. grepfará o que você precisa.
Matt Friedman
Eu atualizei esta resposta com base nos seus comentários. Obrigado a todos.
Kibibu #
81

O seguinte código shell parece funcionar para mim:

if [ "$( psql -tAc "SELECT 1 FROM pg_database WHERE datname='DB_NAME'" )" = '1' ]
then
    echo "Database already exists"
else
    echo "Database does not exist"
fi
Nathan Osman
fonte
1
Eu gosto que você não retransmitir nenhum corte externo grep wc e outras coisas .. você verifica a existência de db, o que supostamente significa que você tem pelo menos psql, ant é o menor e único comando que você usa! muito legal mesmo. Além do assunto, não mencionei o tipo de shell nem a versão ou distribuição dos comandos. Eu nunca usaria uma pletora de tubos em ferramentas de sistema que já vi nas outras respostas por saber disso. Isso leva a problemas de anos depois
Riccardo Manfrin
1
Concordo com @RiccardoManfrin, esta parece ser a solução mais direta.
Travis
Se você precisar fazer isso com um usuário que não seja do postgres, poderá adicionar o usuário -U, mas precisará listar um banco de dados ao qual se conectar, pois nenhum poderia existir. Você pode usar o banco de dados do postgres template1 que sempre existe: psql -U user -tAc "SELECT 1 FROM pg_database WHERE datname='DB_NAME'" template1
jan
No cygwin, o psql adiciona caracteres de controle estranhos à saída ('1 \ C-M') e é necessário verificar se a saída começa apenas com 1: if [[ $(...) == 1* ]]
jan
28
postgres@desktop:~$ psql -l | grep <exact_dbname> | wc -l

Isso retornará 1 se o banco de dados especificado existir ou 0 caso contrário.

Além disso, se você tentar criar um banco de dados que já existe, o postgresql retornará uma mensagem de erro como esta:

postgres@desktop:~$ createdb template1
createdb: database creation failed: ERROR:  database "template1" already exists
Arturo
fonte
10
A primeira sugestão é muito perigosa. O que aconteceria exact_dbname_testexistiria? A única maneira de testar é tentar se conectar a ele.
wildplasser 27/01
6
Esta resposta não é robusta! Imprime (não retorna!) Números diferentes de zero se o termo de pesquisa aparecer em outra coluna. Por favor, veja a resposta de kibibu para uma maneira mais correta de fazer isso.
acjay
1
"grep -w foo" pode fornecer falsos positivos quando existe um banco de dados chamado "foo-bar". Sem mencionar, ele encontrará todas as palavras no cabeçalho de saída do psql.
Marius Gedminas
1
Discordo totalmente desta resposta. SEMPRE será verdade se você usar essa expressão em uma instrução lógica. você pode tentar estes exemplos para testar: psql -l | grep doesnt_matter_what_you_grep | wc -l && echo "true"vspsql -l | grep it_does_matter_here && echo "only true if grep returns anything"
Mike Lyons
2
O que há com todo o corte? Se você quiser ter certeza de que está apenas visualizando a primeira coluna, basta colocá-la no regex:, psql -l | grep '^ exact_dbname\b'que define um código de saída se não for encontrado.
Steve Bennett
21

Eu sou novo no postgresql, mas o comando a seguir é o que eu costumava verificar se existe um banco de dados

if psql ${DB_NAME} -c '\q' 2>&1; then
   echo "database ${DB_NAME} exists"
fi
Bruce
fonte
9
Pode ser simplificado ainda mais psql ${DB_NAME} -c ''.
Pedro Romano
2
Parece bom para mim, embora possa falso negativo se o banco de dados existe, mas você não pode se conectar a ele (perms talvez?)
Steve Bennett
7
@SteveBennett, se você não tem nenhum permissões para o DB necessária, então ele não existe para você :)
Viacheslav Dobromyslov
9

Você pode criar um banco de dados, se ele ainda não existir, usando este método:

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

Estou combinando as outras respostas para um formulário sucinto e compatível com POSIX:

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

Um retorno de true(0 ) significa que existe.

Se você suspeitar que o nome do banco de dados possa ter um caractere não padrão, por exemplo $, precisará de uma abordagem um pouco mais:

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

As opções -te -Agarantem que a saída seja bruta e não "tabular" ou preenchida com espaço em branco. As colunas são separadas pelo caractere de barra vertical |, portanto, o cutou ogrep deve reconhecer isso. A primeira coluna contém o nome do banco de dados.

EDIT: grep com -x para evitar correspondências parciais de nomes.

Otheus
fonte
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
fonte
+1 Para uso esporádico causal, eu optaria pela outra resposta, mas para um script de rotina, isso é mais limpo e robusto. Advertência: verifique se o usuário 'postgres' pode se conectar sem senha.
leonbloy
Sim, há um problema sobre o nome de usuário sendo necessário. OTOH: você não gostaria de usar outra função sem permissão de conexão.
wildplasser
3

Para completar, outra versão usando regex, em vez de corte de cadeia:

psql -l | grep '^ exact_dbname\b'

Então, por exemplo:

if psql -l | grep '^ mydatabase\b' > /dev/null ; then
  echo "Database exists already."
  exit
fi
Steve Bennett
fonte
Usar \btem o mesmo problema que todas as respostas grep -wque usam, como nomes de bancos de dados podem conter caracteres que não sejam constituintes de palavras -e, portanto, as tentativas de correspondência footambém serão correspondentes foo-bar.
phils
2

A resposta aceita de kibibu é falha, pois grep -wcorresponde a qualquer nome que contenha o padrão especificado como um componente de palavra.

ou seja, se você procurar "foo", então "foo-backup" é uma correspondência.

Resposta de Otheus fornece algumas boas melhorias, e a versão curta funcionará corretamente na maioria dos casos, mas a mais longa das duas variantes oferecidas exibe um problema semelhante com a correspondência de substrings.

Para resolver esse problema, podemos usar o -xargumento POSIX para corresponder apenas a linhas inteiras do texto.

Com base na resposta de Otheus, a nova versão fica assim:

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

Dito isso, estou inclinado a dizer que a resposta de Nicolas Grilly - onde você realmente pergunta ao postgres sobre o banco de dados específico - é a melhor abordagem de todas.

phils
fonte
2

As outras soluções (que são fantásticas) perdem o fato de que o psql pode esperar um minuto ou mais antes de atingir o tempo limite, se não conseguir se conectar a um host. Então, eu gosto desta solução, que define o tempo limite para 3 segundos:

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

Isso é para conectar-se a um banco de dados de desenvolvimento na imagem oficial do Alpine Docker do postgres .

Separadamente, se você estiver usando o Rails e quiser configurar um banco de dados, se ele ainda não existir (como ao iniciar um contêiner do Docker), isso funcionará bem, pois as migrações são idempotentes:

bundle exec rake db:migrate 2>/dev/null || bundle exec rake db:setup
Dan Kohn
fonte
1

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

versão mais curta

Justin
fonte
0

Eu ainda sou bastante inexperiente com a programação de shell, por isso, se isso estiver realmente errado por algum motivo, vote-me, mas não fique muito alarmado.

Construindo a partir da resposta de kibibu:

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