fredag 10 juni 2011

Skriv var du vill i kommandoraden med ANSI-sekvenser

I över 10 år har jag undrat hur en del kommandoradsprogram gör för att skriva ut något på en specifik position på skärmen istället för (som det brukar var) längst ned i kommandoraden. T.ex. är det många program som visar en progressbar över hur långt en installationen har kommit eller hur mycket som laddats ned av en fil, se t.ex. wget.

Idag tog jag äntligen mod till mig och googlade fram svaret.

Svaret är ANSI escape-sekvenser. Man skriver helt enkelt ut några speciella sekvenser av tecken för att flytta pekaren eller ändra färg etc.

Här är några exempel på escape-sekvenser för att flytta pekarens position:

\033[r;kH - flytta pekaren till rad r och kolumn k
\033[nA - flytta pekaren n positioner uppåt
\033[nB - flytta pekaren n positioner ner
\033[nC - flytta pekaren n positioner höger
\033[nD - flytta pekaren n positioner vänster
\033[s - lagra nuvarande pekar-position
\033[u - återställ lagrad pekar-position
\033[K - sudda allt på raden från pekarens position

Några sekvenser för att formatera text:
\033[1m - fet eller extra stark färg på
\033[4m - understruken text
\033[7m - förgrund och bakgrund byter färg

Några sekvenser för att byta färg på texten:
\033[31m - röd text på svart bakgrund
\033[41m - vit text på röd bakgrund
\033[32m - grön text på svart bakgrund
\033[42m - vit text på grön bakgrund
\033[33m - gul text på svart bakgrund
\033[43m - vit text på gul bakgrund
\033[34m - blå text på svart bakgrund
\033[44m - vit text på blå bakgrund

Fler kommandon och mer info kan du hitta här, här, här och här.

Ett par exempel med PHP

Så för att t.ex. skriva ut något på en viss position i terminalen med php kan man göra en funktion som ser ut såhär:

function echoAt($row,$col){
  echo "\033[s"; //lagra pekarens position
  echo "\033[".$row.";".$col."H"; //flytta pekaren
  echo $text; //skriv ut texten
  echo "\033[u"; //återställ pekarens position
}

Jag skrev även en lite mer komplicerad funktion som ritar ut en progressbar.

function printProgressbar($percentage,$size=50){
   $filled = round($percentage*$size); //hur mycket ska vara ifyllt?
   $info = number_format($percentage*100, 1, '.', '')."%"; //formatera info
   $infoStart = intval($size/2-strlen($info)/2); //vill ha info centrerad

   $output = "\033[s"; //lagra pekarens position
   $output .= "\033[7m"; //invertera färger
   $output .= "\033[35m"; //sätt färg för ifylld del
   $output .= "\033[1000C"; //gå max till höger
   $output .= "\033[".$size."D"; //gå till vänster

   for($i=0; $i<$size; $i++){
      if($i==$filled) $output .= "\033[37m"; //byt färg för ofylld
      if($i>=$infoStart&&$i<$infoStart+strlen($info)){
         $output .= substr($info,$i-$infoStart,1); //skriv ut info
      }else{
         $output .= " ";
      }
   }

   $output .= "\033[u"; //återställ pekarens position

   echo $output;
}

Om du vill testa funktionen men inte har något tidskrävande jobb att använda den till så kan du testa med följande rader:

for($i=0; $i<=1000; $i++){
        usleep(1500);
        printProgressbar($i/1000.0);
}


Så här ser den ut när den kör:



Hoppas du får någon nytta av detta. Jag har själv börjat lägga in progressbarer i alla jobb som tar mer än några sekunder. Ett litet problem är dock att om man loggar utskrifterna från ett jobb och sedan öppnar det i en texteditor (typ vim) så får man massor av ANSI-sekvenser som gör loggen helt oläsbar. Skriver man däremot ut loggen med cat, tail eller liknande så spelas progressbaren upp i repris.

För windows-användare

Sedan windows NT så används inte ANSI i DOS/kommandotolken. Men du kan aktivera det manuellt.

Öppna c:\windows\system32\config.nt
Lägg till följande rad längst ned i filen:
device=%SystemRoot%\system32\ansi.sys
Starta om datorn.

ANSI Art

Har du inga program som behöver progressbars eller andra nyttiga ändamål för denna kunskap så kan du alltid använda ANSI-sekvenser för att skapa konst.