Como detectar se um comando foi fornecido ao prompt do shell

Como detectar se um comando foi fornecido ao prompt do shell

Quero escrever um PROMPT_COMMANDque responda ao que foi fornecido no prompt de comando imediatamente anterior. Por exemplo, para alternar entre um prompt expansivo e informativo ou um prompt simples e compacto, como este:

mikemol@serenity ~ $ echo hi
hi
$ echo ho
ho
$ echo hum
hum
$
mikemol@serenity ~ $

Os valores só parecem ser adicionados ao histórico do shell se não estiverem vazios, portanto, não posso simplesmente testar se a entrada mais recente do histórico está em branco. Correr set | grep some_commandapós correr some_commandnão me dá nada, então não parece que haja uma variável de ambiente contendo essas informações.

Eu uso principalmente bash, mas ficaria curioso sobre soluções compatíveis com POSIX e outros shells.

Responder1

No final das contas, eu não precisava PROMPT_COMMANDde nada. Obrigado a Christopher por me apontar na direção certa.

Em vez disso, considere este arquivo ps1.prompt:

${__cmdnbary[\#]+$(
    echo '\u@\h: \w' # Your fancy prompt goes here, with all the usual special characters available.
) }${__cmdnbary[\#]=}\$

Posso então alimentar isso em meu PS1:

PS1=$(cat ps1.prompt)

(Você não precisa fazer desta forma, mas achei conveniente para ilustração e edição.)

E assim vemos:

mikemol@zoe:~$ echo hi
hi
mikemol@zoe:~$ echo ho
ho
mikemol@zoe:~$ echo hum
hum
mikemol@zoe:~$ 
mikemol@zoe:~$ PS1=$(cat ps1.prompt)
$ 
mikemol@zoe: ~ $ echo hi
hi
$ echo ho
ho
$ echo hum
hum
$ 
mikemol@zoe: ~ $ 

Estamos usando o hack de arraydemonstrado aqui, mas em vez de usar a substituição de parâmetro bashde ${parameter:-word}, usamos ${parameter+word}assim, acionamos apenas se não houver execução de comando anterior.

Isso requer alguma explicação, pois somos forçados a usar um duplo negativo em nossa lógica.

Como ${__cmdnbary[\#]-word}${__cmdnbary[\#]=}funciona

Na demonstração original do array hack, a construção ${__cmdnbary[\#]-word}${__cmdnbary[\#]=}foi usada. (substituí $?por wordpara maior clareza). Se você não está particularmente familiarizado com expansão de parâmetros e matrizes (eu não estava), não está claro o que está acontecendo.

Primeiro, entenda\#

Poro manual:

\#o número do comando deste comando

...

O número do comando é a posição na sequência de comandos executados durante a sessão atual do shell.

Isso significa \#que só mudaráse e apenas seum comando é executado. Se o usuário inserir uma linha em branco no prompt, nenhum comando será executado e, portanto, \#não será alterado.

A configuração de uma string vazia em ${__cmdnbary[#]=}

${__cmdnbary[\#]=}usa expansão de parâmetros. Retornando parao manual:

${parameter:=word}

Atribuir valores padrão. Se o parâmetro não estiver definido ou for nulo, a expansão da palavra será atribuída ao parâmetro. O valor do parâmetro é então substituído.

Portanto, se __cmdnbary[\#]não estiver definido ou for nulo, esta construção atribuirá uma string vazia ( wordé uma string vazia no nosso caso) e toda a construção será substituída em nossa saída pela mesma string vazia.

__cmdnbary[\#]vaisempreserá unset ou nulo na primeira vez que o virmos, já que # é monotônico - sempre aumenta ou permanece o mesmo. (Isto é, até que ele faça um loop, provavelmente em torno de 2 ^ 31 ou 2 ^ 63, mas há outros problemas que teremoslongoantes de chegarmos lá. Há uma razão pela qual descrevo a solução como um hack.)

O condicional em${__cmdnbary[\#]-word}

${__cmdnbary[\#]-word}é outra expansão de parâmetro. Do manual:

${parameter:-word}

Usar valores padrão. Se o parâmetro não estiver definido ou for nulo, a expansão da palavra será substituída. Caso contrário, o valor do parâmetro é substituído.

Então, se a entrada da matriz em \#fornão definido ou nulo, wordé usado em seu lugar. Como não tentamos atribuir a __cmdnbary[\#](usando a ${parameter:=word}substituição) antes de verificá-lo, a primeira vez que verificarmos um determinado \#deve encontrar essa posição no array não definida.

bashusa matrizes esparsas

Um ponto de esclarecimento para quem está acostumado com arrays estilo C. bashrealmente usamatrizes esparsas; até você atribuiralgopara uma posição em uma matriz, essa posição não será definida. Uma string vazia não é a mesma coisa que "nulo ou não definido".

Por que usamos ${__cmdnbary[#]+word}${__cmdnbary[#]=}

${__cmdnbary[\#]+word}${__cmdnbary[\#]=}e ${__cmdnbary[#]-word}${__cmdnbary[#]=} look very siilar. The *only* thing we change between the two constructs can be found in the first portion; we use${parameter:+word} instead of${parameter:-word}`.

Lembre-se de que with ${parameter:-word}, wordé apresentado apenas se parameterfor nulo ou não definido - no nosso caso, apenas senão tenhodefinir a posição no array ainda, o que não teremos feito se e somente se \#tiver incrementado, o que só acontecerá se apenas executarmos um comando.

Isso significa que, com ${parameter:-word}, só apresentaremos wordsenão tenhoexecutou um comando, que é exatamente o oposto do que queremos fazer. Então, usamos ${parameter:-word}em vez disso. Novamente, do manual:

${parameter:+word}

Usar valor alternativo. Se o parâmetro for nulo ou não definido, nada será substituído, caso contrário, a expansão da palavra será substituída.

O que é (infelizmente) mais lógica duplamente negativa para entender, mas aí está você.

O prompt em si

Explicamos o mecanismo de comutação, mas e o prompt em si?

Aqui, eu uso $( ... )para conter a essência do prompt. Principalmente para meu próprio benefício para facilitar a leitura; você não precisa fazer assim. Você pode substituir $( ... )por qualquer coisa que normalmente colocaria em uma atribuição de variável.

Por que é um hack?

Lembra como adicionamos entradas a um array esparso? Não estamos removendo essas entradas e, portanto, o array crescerá para sempre até que a sessão do shell seja encerrada; a casca está vazando PS1. E até onde eu sei, não há comodesarmaruma variável ou posição de array no prompt. Você poderia tentar $(), mas descobrirá que não funcionará; as alterações feitas no namespace da variável dentro de um subshell não serão aplicadas ao espaço de onde o subshell foi bifurcado.

Vocêpodertente usar mktmpno início da sua tarefa .bashrc, antes PS1da tarefa, e coloque as informações no arquivo resultante; então você poderia comparar sua corrente \#com o que você armazenou lá, mas agora você tornou seu prompt dependente da E/S do disco, o que é uma boa maneira de se bloquear em situações de emergência.

informação relacionada