Bash prompts: the essential 9

Bash is probably the most common command-line shell in the GNU/Linux world. Although a lot of people use alternate shells (such as Zsh), Bash is still shipped with most mainstream distros as the default. Once you have a lot of different remote machines, all running Bash as the shell, it becomes increasingly difficult to pay attention to the prompt, and typing reboot in a machine different from the one you wanted becomes more likely. I deal with that problem by changing Bash prompts…

First of all, the basics: Bash prompts are just environment variables with special characters you can set and export. Bash has four of these variables: PS1 to PS4, but usually only the first two matters (actually, just PS1 – for a reference on the others, check the manpage). The most common PS1 string is:


spectra@home:~$ echo $PS1
\u@\h:\w\$
spectra@home:~$

This has 4 special characters, escaped with a backslash: \u informing us the username; \h informing us the hostname; \w, informing us the working directory; and \$, which gives us the $ in the end of the prompt (more on this later).

So, essentially, one can change that string to anything else…


spectra@home:~$ PS1="my_shell_prompt\$ " 
my_shell_prompt$

Pretty easy. You can check a complete reference of the special characters at the section PROMPTING of bash manpage, but the most useful IMHO are the following:

  • \d the date
  • \t the time (24-hour format)
  • \W the basename of the current working directory
  • \! the history number of this command
  • \# the command number of this command
  • \$ shows # if the UID is 0 (is we are root), or $ for all the rest

Also, as part of the prompt string, one can use ANSI Colors enclosed as non-printing characters (that is between \[ and \]). ANSI sequences always begin with an “ESC[” and end with an “m”. (Yes… Really arbitrary… but that’s the way it is…). ESC can be represented as \e… Here is a list of the most common colors in ANSI sequences:

  • Black: 0;30
  • Red: 0;31
  • Green: 0;32
  • Brown: 0;33
  • Blue: 0;34
  • Purple: 0;35
  • Cyan: 0;36
  • Light Gray: 0;37

Now, notice that there are two numbers separated by a semi-colon… the first is always 0 (zero) in the colors I pointed above, but it actually refers to an ANSI attribute called Select Graphic Rendition… You can use 0 (zero) to normal colors, 1 for bold, 2 to faint, etc. So \e[0;30m refers to BLACK, \e[1;30m refers to DARK GREY. The Wikipedia has a good article on these escape sequences.

Once you’re satisfied with something printed in a color, to go back to the default (to reset), you issue the \e[0m escape sequence.

So, back to my problem… Each different machine gets a different color for the hostname. On “hospital” machine, for instance, my PS1 looks like:


spectra@hospital:~$ PS1="\[\e[1;33m\]\u\[\e[0m\]@\[\e[0;35m\]\h\[\e[0m\]:\[\e[0;32m\]\w\[\e[0m\]\$ " 
spectra@hospital:~$

With \e[0;35m (Purple) for the hostname. On “home” machine, it may be \e[0;34m (Blue)... On “server”, it may be \e[0;36m (Cyan), and so on… After a while, you get used to the color and end up linking the color to the machine… so that typing “reboot” on a machine with the wrong color gets harder than before.

To make the changes permanent, put export PS1 in one of the config script of bash (.bashrc, .bash_profile, etc). On some systems, /etc/environment holds lots of environment variables definitions.

I just scratched the surface… That’s just what works for me… The Bash-Prompt-HOWTO has some interesting examples, and I actually have a friend who uses more esoterical stuff, such as fancybash or bashish, but I’ll leave this up to you…

Referência Rápida de Interface: Biblioteca readline

Pouca gente sabe, mas um boa parte dos programas de linha de commando no GNU/Linux são compilados com uma biblioteca unificada, que serve tanto para a edição de linhas de entrada, quanto para simplificar a API para os programadores. Essa biblioteca chama-se NCurses readline.

“OK, mas por que isso é importante?” deve ser a pergunta que está na cabeça do leitor nesse momento. Isso é importante porque a NCurses readline tem uma interface com o usuário bem simples, e muito prática. Quantas vezes você está na linha de comando e descobre que errou algo no meio do caminho e decide apagar a linha? O que você faz? Pressiona Backspace e fica segurando até a linha desaparecer? Ora, muito mais prático teclar Control-U. Esse atalho apaga a linha inteira muito mais rapidamente.

Bem, talvez apagar uma linha no prompt do shell não seja um exemplo de poder, mas o mesmo acontece com senhas… Você está digitando uma senha quando percebe que no meio do caminho errou algo… como o programa não mostra a senha sendo digitada, como você apaga ela e digita de novo aproveitando o mesmo prompt? Isso mesmo: Control-U!

Há uma série de outros atalhos poderosos relacionados a biblioteca NCurses readline, e, uma vez que você tenha decorado uns poucos, a agilidade na linha de comando e em programas relacionados acaba multiplicada. Esses atalhos são padronizados há tanto tempo que mesmo programas não compilados com a NCurses readline acabam implementando os mesmos!!! Pensando nisso, aqui vai uma referência rápida dos que mais utilizo:

  • Control-U: Apaga os caracteres do ponto onde está o cursor até o início da linha.
  • Control-K: Apaga os caracteres do ponto onde está o cursor até o fim da linha.
  • Control-A: Move o cursor para o início da linha.
  • Control-E: Move o cursor para o fim da linha.
  • Control-B: Move o cursor um caractere para trás. Extremamente útil quando está nesses terminais que não têm setinhas ou que não as implementam com os códigos corretos.
  • Control-F: Move o cursor um caractere para frente.
  • Control-D: Deleta o caractere sob o cursor (igual a teclar Del em um sistema DOS).
  • Control-H: Deleta o caractere anterior ao cursor (igual a teclar Backspace em um sistema DOS). Esse tenho usado muito ultimamente: minha tecla Backspace está com problemas…
  • Control-J: Termina a janela (ou, no caso de uma linha, termina a linha). No caso de um terminal, é como dar um ENTER.
  • Control-O: Insere uma nova linha na posição do cursor. Também, no caso de um terminal, é como dar um ENTER.
  • Control-L: Faz um refresh na tela. Muito útil quando você está um Terminal gráfico e a saída do programa anterior te deixa com um prompt no meio de um monte de caracteres.
  • Control-N: Move o cursor uma linha abaixo.
  • Control-P: Move o cursor uma linha acima.

Existem outros atalhos, mas esses são de longe os mais úteis. Eu gosto muito da linha de comando, e costumo dizer que uso o X apenas como um multiplexador de terminais (na realidade, algumas vezes evito o X completamente e uso o GNU Screen – mas isso é outra história). Se você também gosta da linha de comando mas ainda não conhecia os atalhos, espero ter contribuido…

Para os novatos ou os que não apreciam a interface em linha de comando, deixo um link de um artigo interessante: The Command Line: The Best Newbie Interface?.

Uma última dica: Como eu disse antes, vários programas implementam esses atalhos. Para o problema da digitação da senha que referi acima, se você estiver no GDM, o Control-U vai funcionar igualzinho a linha de comando! ;-)

Update 2008-10-21 10:20:00: Como apontado pelo comentário do bart9h, é a biblioteca readline e não a NCurses que faz isso. Obrigado, bart9h.

Petição, protestos e mais programação

Parece que um pessoal vai aproveitar que o Senador Azeredo estará em Porto Alegre para entregar-lhe uma versão impressa da petição que está rolando online. Na dificuldade de encontrar os autores da petição para pedir-lhes uma cópia do banco de dados (somente os autores têm acesso aos dados brutos), me perguntarm se eu conseguiria uma solução…

Bem, eu respondi o que eu sempre respondo… “Está na Internet? Publicamente? Então é possível.”. Aproveitei alguns minutos ociosos e rodei um script para pegar as informações (obviamente em Ruby). O bichinho ficou tão bom que resolvi rodá-lo a cada 30 minutos, mantendo um banco de dados “paralelo” do que está online… A saída dele deixo disponível aqui… Vai que outras pessoas precisam também:

veto2008.csv.gz – Arquivo compactado CSV com os dados da petição na seguinte ordem: número da assinatura, nome, cidade/estado, e comentario.

veto2008.marshal.gz – Para os programadores Ruby, este é um arquivo compactado com um Hash serializado no seguinte formato:

hash[numero_da_assinatura] = { :nome => "nome_do_individuo",
                               :cidade => "cidade_e_estado",
                               :comentario => "comentario" }

# Para desserializar (precisa descompactar antes...)
hash = Marshal.load(File.read('veto2008.marshal'))

Refresh Machine em Ruby

Eventualmente todos enfrentaremos esta situação: você tem um objeto com “estados” que são lidos de uma fonte externa através de pooling. Usualmente você simplesmente adiciona uma thread no construtor do objeto e coloca dentro dela um loop infinito que verifica a cada intervalo de tempo se existe estado novo e, se for o caso, atualiza o estado do objeto.

Essa é uma construção muito simples e bastante utilizada, no entanto tem um problema de escalabilidade importante: para cada objeto você tem uma thread e um loop infinito. Se você tem poucos objetos, isso não chega a ser um problema… mas se tem muitos, aí é outra conversa.

Alguns vão argumentar que não é obrigatório usar essa construção, que dependendo da fonte dos dados, ela pode “atualizar” o objeto, em um padrão Observer, o que está mais do que correto… Mas nem sempre controlamos a fonte dos dados, e nem sempre ela é inteligente assim (na realizade, quando não a controlamos ela parece bantante burra!). Foi para isso que programei uma “Refresh Machine”: um container para objetos que precisam ser atualizados através de pooling. Ela presume duas coisas: que esse objeto tenha um método/atributo refresh que armazene o intervalo em segundos entre as atualizações, e um método do_refresh, que será executado entre os intervalos.

Vamos ao código:


require 'thread'
class RefreshMachine

  attr_accessor :wait

  def initialize(wait = false)
    @wait    = wait
    @killall = false
    @queue   = Queue.new
    @removed = Array.new
    @thread  = Thread.new do
      loop do
        ( @removed.clear; @queue.clear; @killall = false ) if @killall
        next if @queue.empty?
        object, last_refresh, thread = @queue.deq

        # Helps the garbage collection
        thread = nil if (! thread.nil?) and (! thread.alive?)

        # Three things can happen with a dequeued object
        if (Time.now < (last_refresh + object.refresh)) or
           (! thread.nil? and @wait)
          # First: It's too early to refresh it or we still have
          #        a refresh thread running and have to 'wait', so we
          #        just put it back in the queue
          @queue.enq [ object, last_refresh, thread ]
        else
          if @removed.include?(object)
            # Second: We have a "remove request" for it, so we
            #         delete the request and avoid queueing it again
            @removed.delete(object)
          else
            # Third: It's time to refresh it, so we
            #        call do_refresh and put it back in the queue
            add(object)
          end
        end
      end
    end
  end

  def add(object)
    @queue.enq [ object, Time.now, Thread.new { object.do_refresh } ]
  end

  def del(object)
    @removed << object unless @removed.include?(object)
  end

  def killall
    @killall = true
  end

end # of class RefreshMachine

O objetivo é usar o mínimo possível de threads com loops infinitos…. e ficou pequena o bastante para blogar a respeito :-)

Exciting new World

I’ve just tested the improvements in the performance of Javascript in Firefox 3 and WOW! Javascript in FF3 is really fast. While googling about it I just ran across a recent interview with Brendan Eich about the future of Javascript and I got excited about two things about this future.

First was what we already have, still in the beginning, but with a lot of potential: HotRuby. Really interesting to script a webpage in Ruby (which is my favorite language) and, while it’s not embedded the way Javascript is, it gets “compiled” in the server side with YARV (the new bytecode compiler for the next version of Ruby, 1.9), and then served to the browser in the form of JSON objects, so it can be interpreted by the Javascript engine in it. All this is transparent and work with XMLHttpRequest. It’s not a coincidence that Eich mentions it as being a form of ARAX (changing the J in AJAX for R – from Ruby).

I already do a lot of coding in Ruby… not having to deal with Javascript anymore is surely a plus. ITOH, Eich is talking about improvements in Javascript that would render it as a real programming language… Maybe coding in it would not be so painful anymore by then ;-)

The whole interview have to do with this Project Tamarin, a “high-performance, open source implementation of the ECMAScript 4th edition (ES4) language specification” [ ECMAScript 4 is the same thing as Javascript 2 ] by the Mozilla developers. And this is the second thing I got excited for: they’ve planned to glue IronRuby (Ruby compiler for argh! .NET) to it via IronMonkey.

So… exciting news! Either via Tamarin or via HotRuby, we’ll get Ruby browser scripting. My “free mind” tends to favor HotRuby instead of IronRuby/IronMonkey/Tamarin… But in the end what matters is that all those people now cursed by Javascript will finally have a taste of what a real programming language feels like.. Who knows! They might even like it ;-D

Ruby security advisory and fix

Debian 4.0 version of Ruby is open to the, now widely known, Ruby security vulnerabilities. The bug is reported as 487238 in Debian’s BTS, and is closed, since the version now in sid (version 1.8.7.22-1) is already fixed. Users of stable can apply the patch provided by Daniel Franke (it doesn’t seem to fix all, but goes a long way).

Apparently, this brought up (again) the rants over full disclosure. Indeed, what is vulnerable is not that hard to find, as Zed Shaw showed us, so, why not talk about it in a plain and bold form? Why just provide the CVE numbers and ask for everybody to upgrade? Zed goes more deep about the quality of C code, but that is not the issue I want to talk about…

As a Free and Open Source Software supporter (and developer), I can see the benefits of full disclosure. As a not-full-time webmaster, I can see the benefits of not having a “proof-of-concept” piece of code attached to the vulnerability report. Of course, there’s a lot of things a webmaster can do to prevent having a machine completely compromised in case a security advisory is published with a proof-of-concept code in it (think about chrooting, randomized memory protection, security libraries, grsecurity, SELinux, etc) – and my machines, although vulnerable to the bug, would not be fully compromised if exploited.

I guess one should be prepared to whatever comes from the Internet… Full disclosure, in this sense, have more pros than cons, IMHO. For instance it was not clear if Debian 4.0 were vulnerable… There were no security advisory coming from Debian (and there’s still not), and it is not promptly obvious if the version packaged is affected. I know that at least I wanted to run a proof-of-concept to check if my server is vulnerable or not before going all the way into packaging a fix (or backporting the sid version), and it was not until I read Matasano Chargen Blog that I could test older versions. But different people have different ideas…

The truth about Unifying Solutions

Everytime I read about a new supposed unifying solution for any given problem I have that feeling that something will go wrong. And I end up being unmistakenly right. But re-reading Joel On Software about How Microsoft Lost the API War has given me the right quote to put that feeling in words:

"But the idea of unifying the mess of Visual Basic and Windows API programming by creating a completely new, ground-up programming environment with not one, not two, but three languages (or are there four?) is sort of like the idea of getting two quarreling kids to stop arguing by shouting "shut up!" louder than either of them. It only works on TV. In real life when you shout "shut up!" to two people arguing loudly you just create a louder three-way argument."

"(By the way, for those of you who follow the arcane but politically-charged world of blog syndication feed formats, you can see the same thing happening over there. RSS became fragmented with several different versions, inaccurate specs and lots of political fighting, and the attempt to clean everything up by creating yet another format called Atom has resulted in several different versions of RSS plus one version of Atom, inaccurate specs and lots of political fighting. When you try to unify two opposing forces by creating a third alternative, you just end up with three opposing forces. You haven't unified anything and you haven't really fixed anything.)"

Just think a little bit and I bet you'll realize how many things happens this way. We have A and B competing. Any attempt to kill A or B by creating C will end up having A, B and C competing.... Yes, I am talking about distro wars, emacs and vi clones, browser wars...

Mapping Tree-like structures to tables

I've been messing with ActiveRecord (from the Ruby on Rails Framework) for some time now, and I must state that I love it. Although I don't use the full Rails framework (I like to use other kind of approach to render my webpages), the ORM pattern ActiveRecord implements is killer!

Well... for one of my pet projects I needed to map tree-like structures (like one-to-many objects connections) to tables, and I used ActiveRecord as my backend. This is the module that I came up with. It's still rough and need a lot of polish and documentation, but is a general purpose module for the ActiveRecord. Contributions are welcome.

No error-testing were included. The 0.51 release is a "make it work" release. I wish I could figure out how to make it work with database backends other than MySQL, but my SQL skills have not allowed me to understand REGEXP calls in other backends. I've stripped out the unit testing also, since I found those were not "releaseable" tests. They can come in in a future release, who knows.

Happy Hacking.

I love Ruby

require 'open-uri'
require 'timeout'

begin
    status = timeout(30) {
        file = open("http://www.debian.org/")
    }
rescue TimeoutError
    puts "Oops!"
end

And people still ask me why I love Ruby

Time is ticking

I like countdown clocks. They sort of give you a pretty good idea of how much you have to wait (or work) to accomplish one particular goal.

For instance, here I can watch for how long I still have to be a slave (I am under a residency trainning in radiology). Here I can see that I have no longer much time to help with DebConf4 (and that means I still have to work a lot).

I guess I am just tired of advancing calls...

Perens on MyDoom virus

I think everybody have already read Perens' letter wrt the MyDoom virus and how he thinks the community must behave in such ocurrences.

Well, I've translated it into portuguese, to help spread his word among this language FLOSS comunity. It's at my company's website.

Hackers in TV

I've been contacted few days ago to help (along with other 3 pretty good hackers I know: Marlon Dutra, Fernanda Weiden, and Christiano Anderson) in the making of a TV series about the hacker community and the FLOSS movement.

We've scheduled a meeting tomorrow, but we've already discussed somethings: firstly, the stream is going to be freely available (so I can reproduce it in my website... yes!!!); and secondly, we'll start from the beginning (and try to undo the bad fame the word "hacker" have), but we intend to go much further in the subject.

This is going to be a heck of experience. I am already looking forward to have some streans to put online.

New set of scripts

In the middle of my website migration, I've disliked the way things were going. So I decided to rewrite it again. This is it's new version.

Not only disliking made me rewrite it. Two other things contributed for it: XHTML and Planet Debian.

Now it's all XHTML Transitional compatible (you can check it at W3C Validator). I strongly believe in Mark-Up Languages based on XML, so, I forced myself into putting everything in XHTML.

The raise of Planet Debian, besides making be change the right feeder to Planet Debian, also made me code a RSS Feeder to syndicate it. And since my webplace may start getting more visitors, I couldn't let it the way it was.

Surely, I have added just a few new contents (I am still migrating it, remember?). But soon we'll have all the articles and the contents back in place...

Enjoy the site. Enjoy Planet Debian...