Newsgroup: it.comp.os.linux.iniziare
Titolo: [Bash] Allineare numeri a destra

Sto rielaborando dei file di testo costituiti da colonne di numeri e
date. Questo file viene costruito partendo da un file singola colonna
e aggiungendoci davanti (a sinitra) una colonna con la data e a destra
una colonna con un altro valore.
 
Esempio:
 
 
file1.txt
_________
 
123.0
3.2
...
...
________
 
 
 
file2.txt
_________
 
dd/mm/yyyy     123.0     500
DD/mm/yyyy       3.2      20
____________________________
 
 
Invece ecco come viene quando aggiungo la
data e lultima colonna:
 
 
dd/mm/yyyy     123.0     500
DD/mm/yyyy     3.2       20
 
 
(tra i campi ho inserito delle tab piuttosto
che degli spazi).
Questo sembra garantire un allineamento ordinato
seppure a sinistra delle varie colonne.
 
 
 
La prima cosa che mi viene in mente è che prima occorra mettere apposto
l'allineamento del file di partenza e poi aggiungervi le altre due
colonne.
 
Ho letto in giro che si può fare qualcsa con printf.
Ma le soluzioni che ho letto funzionano se si conosce il numero massimo
di cifre (virgola compresa) a priori. E io invece non lo so in anticipo.
Vorrei che l'allinamento venisse fatto automaticamente.
 
Avete qualche suggerimento?
Grazie.

 Vorrei che l'allinamento venisse fatto automaticamente.
 
Mi spiace deluderti ma nulla si fa per magia; devi essere tu a programmare
con PERL, AWK o BASH, specialmente quando non dai nemmeno le 
informazioni di base per aiutarti.
 
 

 hai combinato?
 
Ho fatto l'esempio del file di partenza e di quello
di arrivo. Come viene fuori e invee come vorrei che
saltasse fuori.
 
In effetti non ho detto come l'ho ottenuto.
Scendo nel dettaglio.
Abbiammo qusta colonna di valori numerici in un
file txt chiamiamolo "valori.txt".
__________
 
valori.txt
----------
12.30
4.50
678.90
...
__________
 
 
Ogni valore, cioè ogni riga del file si riferisce
ad un giorno. Sono registrazioni giornaliere.
Ma potrebbe essere anche il conto della spesa
di ogni mattina per capirci.
Ad ognuno vogliamo mettere davanti la sua data
sapendo la data cui corrisponde il primo valore.
Ecco come avevo fatto:
_________________
 
add-date.sh
-----------
inizio='19900101'
gg=0
for riga in $(< $1)
do
  echo $(date -d "$inizio + $gg days" '+%d-%m-%y') $riga
  let gg++
done
_________________
 
L'utilizzo è il seguente:
 
$ add-date.sh valori.txt > datato.txt
 
 
In pratica le due colonne vengono scritte con
"echo".
 
Circa l'ultima colonna, semplicemente
contiene un valore numerico che varia
ogni 6000 righe circa.
Per cui è praticamente un numero fisso.
Numero che ho aggiunto in coda ad ogni
riga con "sed" separandolo con uno spazio
dal resto.
Il numero da inserire si sa a priori,
mettiamo che sia per esempio 567:
 
sed 's/$/ 567/' datato.txt
 
 
Tu dicevi che l'allineamento automatico
non è possibile senza un controllo a priori
però invece io mi sono accorto che con
i caratteri "tab" si riesce ad allineare
almeno a sinitra il testo senza sapere niente
in anticipo.
Infatti dallo script sopra salta fuori (esempio):
________________
 
datato.txt
----------
01-01-90 12.30
02-01-90 4.50
03-01-90 678.90
...
________________
 
Se io aggiungo l'ultima colonna usando il comando
sed come sopra, cioè con lo spazio come delimitatore
di campo, ottengo il seguente:
----------------------------
$ sed 's/$/ 567/' datato.txt 
01-01-90 12.30 567
02-01-90 4.50 567
03-01-90 678.90 567
-------------------
 
 
Se invece al posto dello spazio metto un "tab":
-----------------------------
$ sed 's/$/\t567/' datato.txt 
01-01-90 12.30  567
02-01-90 4.50   567
03-01-90 678.90 567
-------------------
 
 
Automagicamente l'ultima colonna salta fuori
allineata, benchè i campi che la precedono non
lo siano.
Penso di aver dato tutti i dettagli delle
operazioni che ho fatto.
 
Ad ogni modo per l'allineamento a destra della
colonna centrale da come mi dicevi nella precedente
risposta non c'è un sistema tipo quello
del "tab" che in qualche modo sistemi le cose
senza ricorrere alla formattazione "manuale".
Mi confermi?
 
Grazie!  :)

 ____________________________
 
Guarda io avevo un'esigenza simile e avevo risolto artigianalmente,
perche' conosco pochissimo awk e nulla perl. Ti do la riga, se non trovi
altro di meglio dovresti risolvere, magari almeno fino a che un esperto
non te lo dia con awk o perl o altro. Anzi, la scrivo andando a capo
tipo script:
 
-------------
for j in $(cat file1.txt); do
echo -en "  $j\n" | rev | cut -c-5 | rev >> pippo
done
---------------
 
$: cat /tmp/pippo
123.0
  3.2
 
E adesso ogni riga di pippo e' un campo lungo 5 caratteri.
 
-- 
Tanti saluti
 


se non vuoi imporre una grandezza massima sul numero delle cifre sfido 
chiunque a trovare una soluzione
 
 
 
 

 ---------------
 
Ho tolto il "-n" e il "\n", c'erano perche' davo la riga in due tempi.
Ovviamente puoi aggiungere piu' spazi prima (se ci sono migliaia) e/o
aumentare i caratteri "-5" se magari, come adesso ho visto nell'altro
post, vai con 2 decimali invece di 1.
 
-- 
Tanti saluti
 

 chiunque a trovare una soluzione
 
Infatti e` assurdo ma visto che le stringhe sono memorizzate in un file
di testo credo sarebbe possibile determinarne la lunghezza massima
leggendole una alla volta e stabilendone la lunghezza con qualcosa di
simile a:
 
stringa=012345; echo ${#stringa}  # 6
 
 
 


Ok gente. Pensavo che ci fosse qualche
modo che io per mia ignoranza magari
ignoravo.
Non c'è un modo semplice per allineare
a sinistra una colonna di numeri, senza
sapere quante cifre hanno quei numeri?
 
Ok, benissimo allora cercherò un modo un
po' meno semplice per farlo.
 
A dirla tutta, l'allineamento a destra
non era una mia esigenza stringente, ma
solo una maggiore precisione estetica,
qundi non intendo sbattermi troppo.
 
I suggerimenti che avete messo sono
un ottimo punto di partenza.
 
Vi ringrazio e magari vi farò presente
la soluzione che ho impiegato.
 
Alla prossima!  :)


Ma cosi imposti una dimensione massima  con la differenza che non è statica
Tra l' altro il file deve essere letto due volte (o caricato in memoria 
prima per elaborarlo dopo)
 
 

 sapere quante cifre hanno quei numeri?
 
Se non  sai la dimensione massima la devi determinare in qualche modo 
altrimenti il problema non è risolvibile
Il motivo è che l' allineamento a destra di un numero con infinite cifre 
comporta una riga di lunghezza infinita
 
[CUT]
 

 Vorrei che l'allinamento venisse fatto automaticamente.
 
Come ti e` stato detto automaticamente non e` possibile ma tu hai un
file dove sono immagazzinati i dati per cui leggendolo puoi stabilire la
lunghezza massima delle stringhe.
Tipo, se hai i dati contenuti nel file "prova.txt":
--------------------------------------------------------------------
#!/bin/bash
lung_max=0
while read line  # Legge in prova.txt una linea alla volta
do
 stringa=${line}
 lung_stringa=${#stringa}  # Determina la lunghezza della stringa
 echo $lung_stringa
 if [ "$lung_stringa" -gt "$lung_max" ]  # verifica se e' maggiore
 then
     lung_max=$lung_stringa
 fi
done < prova.txt
echo
echo "Lunghezza massima: $lung_max"
--------------------------------------------------------------------
 
A quel punto credo che tu possa ispirarti all'esempio di tabella
stampato appunto con printf, come hai detto tu, che trovi in fondo a
questa pagina:
 
http://linuxconfig.org/bash-printf-syntax-basics-with-examples
 
 

 altrimenti il problema non è risolvibile
 
Siccome quell'allineamento serviva anche a me, l'ho risolto in questo
modo che ora dico a te, perche' sembra che all'OP non interessi piu'
(non mi ha neanche risposto).
 
 
1) Ecco il mio script allinea:
-------------------------
#!/bin/bash
 
LUNG=$(sort -gr pippo | head -n 1 | awk '{print length}')
for j in $(cat pippo); do
echo -e "                                               \
                                                        \
                                                        \
                     $j" | rev | cut -b-$LUNG | rev >> $1
done
----------------------------------
 
 
2) Ecco il file d'input da allineare:
---------------------
12345678.00
1234567.00
9.00
723456.00
1234567890.00
12345.00
1234.00
123.00
823456789.00
12.00
----------------
 
3) Questo e' l'output versato in pluto con: ./allinea pluto
-------------------
  12345678.00
   1234567.00
         9.00
    723456.00
1234567890.00
     12345.00
      1234.00
       123.00
 823456789.00
        12.00
----------------
 
Mi sa tanto che lo puo' fare solo awk e in un colpo solo, ma bisogna
essere molto pratici se no ci vuol troppo tempo a provare. Pero' c'era
anche un detto sulla triade cut grep sed, te lo ricordi?
 
-- 
Tanti saluti
 

 Mi confermi?
 
Chi ha mai detto questo. Usa un editor decente puoi fare quanto
hai detto con poca fatica e con poco tempo. Tra l'altro i vari word 
processor usano le proprio le tabulazioni per allineare le parole e
puoi allineare dove vuoi (LaTeX usa '&' ma è un altro pianeta). Ma 
se vuoi ricorrere alla bash devi fare a mano.
 
E infine mi ripeto: Se devi elaborare l'output in un momento 
successivo non aggiungere formattazione.
 
 
 
 
 

 chiunque a trovare una soluzione
 
Premesso che col tramonto delle schede perforate formattare
l'output serve solo in fase di stampa e programmi come LaTeX
(ma anche Open Office e Word) sono in grado benissimo di 
gestire la cosa,la soluzione è banale:
 
- Si genera (con paste, con una SELECT o con un sarcazzo 
  qualsiasi) un file CSV o un formato simile
 
- Al primo passo si calcolano le grandezze dei vari campi
 
- Al secondo passo si applica la formattazione
 
Nulla di nuovo, compilatori come TeX fanpo proprio così 
da circa una trentina d'anni e la tecnica era vecchia come
il cucco già allora.
 
Poi se vuoi esistono anche tecniche di Look Forward 
 
 

 
 
Ed appena postato è venuto fuori un bug. Riposto tutto:
 
awk '
  BEGIN {maxl=0;maxd=0}
    {
      l = length($0)
      if (l>maxl)
        {maxl=l};
      i = index($0,".")
      if ( i > 0)
        {
          d=l-i
          if (d>maxd)
            {maxd=d} 
       }
   }
  END { print maxl,maxd};'  valori.txt
 
 
--- news://
freenews.netfront.net/
 - complaints: 
ne...@netfront.net
 ---

 L'allineamento è solo un effetto collaterale (tra l'altro voluto).
 
 
Uhmmm....
Ma come accade questo effetto collaterale?
È una magia della bash?
Perchè io se faccio un sed e sostituisco
gli spazi con le tabs vedo a schermo
effettivamente un allineamento a sinistra:
 
 
$ cat pippo.txt 
123 456 789
12 34 56
 
$ sed 's/ /\t/g' pippo.txt
123     456     789
12      34      56
 
 
 
Ho scritto davvero troppo.
Scusate la lunghezza...
Ogni vostro intervento l'ho apprezzato.
Grazie a tutti delle risposte.
Spero che magari anche qualcun'altro possa
prendere degli spunti da questo tread.  :)


 
Non conosco questo detto
 
 
 


Soluzione banale ?
Si se la dimensione massima è predeterminata (a runtime od in modo statico)
Anche le varie tecniche come il backtracking o il look forward sono 
aggiustamenti futuri quindi ricadono nel calcolo a runtime
Altrimenti la soluzione è impossibile
 

 È una magia della bash?
 
No, è un residuo della tecnologia delle macchine da scrivere passato
prima alle telescriventi e poi ai terminali dei calcolatori.
 
Con le macchine da scrivere si impostavano prima i punti di tabulazione
spostando il carrello alla posizione voluta e premendo lo stop alle righe
successive premendo la tabulazione il carrello si fermava nello stesso 
punto. Ora volevi allinearti a sinistra? Scrivevi. Volevi allinearti a 
destra? Tornavi indietro di un tot caratteri e fatto. Volevi allineare la 
virgola? Credo tu abbia capito. E ti ho spiegato anche come si fa ad 
allinearsi senza la printf.
 
Le telescriventi non fecero che copiare le macchine da scrivere ma 
copiare il sistema delle tabulazioni era impossibile quindi fecero una 
scelta di compromesso; tenere le tabulazioni ma fissare i punti di stop
una volta per tutte ogni otto caratteri. 
 
In seguito le telescriventi vennero collegate ai computer e quando si 
passò ai terminali video il comportamento venne mantenuto traducendo
la tabulazione con un numero variabile di caratteri da uno a otto.
 
Però fai attenzione, questo comportamento descritto non è sempre 
seguito, ci sono programmi o stampanti che semplicemente convertono
le tabulazioni con un numero fisso di spazi due, quattro, otto o hanno 
comportamenti ancora più bizzarri. 
 
Tornando alla bash, non è la bash che interpreta i caratteri quanto il 
terminale stesso (o il suo driver), per cui ho parlato di effetto 
collaterale.


 chiunque a trovare una soluzione
 
 
E noi (io e anche Yoda) abbiamo calcolato e non imposto la grandezza massima 
come da te richiesto. Ora stai tergiversando per orgoglio e arroganza.
 
 
 

 arroganza.
 
Fosse la prima volta..


La grandezza massima l' hai impostata a runtime
 
 


Posto questo mio tentativo, per ringraziare delle risposte
e chissà che non torni utile a qualcuno in futuro.
Abbiamo la colonna di numeri contenuta in un file di testo.
 
L'idea è questa:
 
1- calcolo con "wc -L" la lunghezza della riga più lunga
 
Questo penso che sveltisca di un bel po' le operazioni.
Evitare la lettua riga per riga, il "while read" per
capirci, rende il tutto più rapido e su file con molte
righe la differenza m'è sembrata palese.
 
2- a questo punto riscrivo il file con awk e printf
impostando il formato in modo coerente rispetto alla
lunghezza del numero o stringa più estesa, allineando
così il tutto a destra.
Ho trovato qualche intoppo nel passare allo script awk
il valore della variabile ${lenght} ottenuto al punto
"1". Sono giunto all conclusione che sia necessario
adottare la forma:
 
"'${lenght}'"
 
Oppure si usa lo switch -v di awk per passare la
variabile dentro il sottoscript. Anche in quel caso
servono le " ". (non ho ben capito perchè sinceramente).
 
 
 
Detto in bash:
 
_________
 
#!/bin/sh
 
 
file=$1
lenght=$(wc -L < $file)
awk -v "len=$lenght" '{ printf "%"len"s\n", $1 }' $file
 
 
# awk '{ printf "%"'${lenght}'"s\n", $1 }' $file
_______________________________________________________
 
 
In pratica la questione così si risolve in 3 righe.
Ma la cosa importante secondo me p che usando awk
l'accesso al file da riallineare a destra è più
rapido che col costrutto while read.
 
Spero che sia utile! :)
Se avete osservazioni fatevi pure avanti!
Alla prossima!
 

 anche se...
 
Ok, al di là dello stile però c'è di più e ben peggio
Tu dici "anche se...".
Io dico "anche se ci mette un'eternità"...
 
Ti mostro ilseguente codice.
Ormai ne avrai le scatole piene immagino..
 
È la prima parte dello script che prende il cvs
ne estrapola la colonna dei dati e la piazza in
un file "valori.tmp".
Poi crea un secondo file "date.tmp".
Ed infine incolla le due colonne con paste
aggiungendovi in coda l'id della stazione in
terza colonna e riformattandole attraverso awk
e printf...
 
Prima di postare il tutto vorrei fermarmi su
un punto che ritengo "debole" in termini di
velocità.
Mi riferisco alla creazione del file delle
date... Ci mette troppo.
Ne ho provati due metodi, uno molto più rapido
dell'altro, ma spero migliorabile con qualche
alternativa che magari tu o qualcun'altro qui
sà suggerirmi.
 
Posto il pezzo di script:
 
________________________
 
#!/bin/sh
 
 
cvsfile=$1
station='Nome stazione'
id=478
 
# Remove first 3 lines
#
cut -d' ' -f2 "$cvsfile" \
  |sed '1,3d' |tr , . > values.tmp
 
# Count line numbers (number of days)
#
days=$(wc -l <values.tmp)
 
# Build date file
#
# first Ver (quicky)
#
#start_date='19910101'
#for (( i=0; i<$days; i++ ))
#do
#  echo $(date -d "$start_date + $i days" '+%d-%m-%y')
#done > days.tmp
#set -x
 
# snd ver (slower)
#
now=$(date -d 19510101 +%Y%m%d)
end=$(date +%Y%m%d -d "$now + $(($days-1)) day")
while [ "$now" != "$end" ]
do
  date -d "$now" +%d/%m/%Y
  now=$(date +"%Y%m%d" -d "tomorrow $now")
done >days.tmp
date -d "$now" +%d/%m/%Y>>days.tmp
 
exit
_________________________________________
 
 
Vi chiederei:
dovendo creare una colonna di date partendo
dalla data iniziale nota e per ogni giorno
successivo per un totale di giorni calcolato
con "wc -l", $days.
 
Avreste qualche alternativa più rapida?
 
Io provando una seconda versione ho peggiorato
le cose. Tra l'altro c'è un errore perchè mi
stampa un giorno in meno, e l'ho aggiunto in
coda fuoi dal ciclo while... non è il massimo
della vita, ma correggerò.
 
Spero in qualche suggerimento per sveltire
le operazioni.
 
Stiamo andando un po' fuori dal seminato
rispetto al titolo del topic, ma pazienza
via...
 
Grazie in anticipo!  :)


Mi rispondo da solo.
Ho trovato un tool chiamato dateutils:
http://www.fresse.org/dateutils
 
 
Con questo ho risolto in qualche modo
il fattore lentezza.
Ecco come verrebbe il pezzo:
 
_________________
 
# Build date file
#
dstart=1991-01-01
days=$(wc -l <values.tmp)
toadd=$(($days-1))
dend=$(dadd $dstart ${toadd}d)
dseq ${dstart} ${dend} -f %d-%m-%y > days.tmp
_____________________________________________
 
Praticamente il numero di giorni lo calcola
contando il numero di righe (di registrzioni
giornaliere, per capirci).
La data iniziale è nota e definita all'inizio.
Calcola i giorni da aggiungere alla data iniziale
per determinare poi la data finale con il comando
"dadd", che fà sempre parte delle dateutils.
A questo punto usa "dseq" che serve proprio
per stampare un range di date sapendo l'inizio
e la fine.
Come vedete si può già selezionare il formato
desiderato in uscita.
 
Per i dettagli "man dseq", è fatto bene e con
esempi.
 
 
Di seguito metto tutto lo script nella sua
versione ultima. Testato mi pare che dia il
risultato necessario alle mie esigenze.
Ho fatto qualche prova con "time" e la
velocità di esecuzione di tutto l'accrocchio
è davvero migliorata.
Ora anche lavorare su file di 10000 righe
è molto comodo e sbrigativo.
 
_________
 
#!/bin/sh
 
 
cvsfile=$1
station='Nome Stazione Registrazione'
id=478
dstart=1991-01-01
 
 
# Remove first 3 lines
#
cut -d' ' -f2 "$cvsfile" \
  |sed '1,3d' |tr , . > values.tmp
 
 
# Count line numbers (number of days)
#
days=$(wc -l <values.tmp)
 
 
# Build date file
#
toadd=$(($days-1))
dend=$(dadd $dstart ${toadd}d)
dseq ${dstart} ${dend} -f %d-%m-%y > days.tmp
 
 
# Paste files - prepend title
# - apply format - add id on last column
#
printf '\n\n\n\n\n\n\n\n'"$station"'\n\n\n\n\n'
paste days.tmp values.tmp \
    | awk '{ printf "%38s%24.3f%8.1f\n", $1, $2, '"$id"' }'
 
 
# remove tmp files
#
rm days.tmp values.tmp
______________________
 
 
 
Ok, a voi ulteriori commenti, suggerimenti
ecc.
Grazie ancora di tutto!

 e printf...
 
Io farei tutto in PERL lasciando perdere la BASH.
Le funzioni di AWK per il trattamento delle date sono
mktime, strftime e systime, con queste è facile generare 
le date senza dover separare e riunire i file. Ecco un 
abbozzo di programma:
 
BEGIN 
  {
    giorni=0;
    datainizio=mktime("1991 01 01 00 00");
    ...
  }
 
/^ *[0-9]/ 
  {
     data = datainizio+giorni*86400;
     giorni++;
     ...
     printf "38%s%24.3f%8.1\n" strftime("%d/%m/%Y",data),$2,id)
     ...
  }   
 
A te continuare


 A te continuare
 
Scusa una cosa...
Il codice precedente che hai abbozzato è Perl o Awk?

 rapido che col costrutto while read.
 
Con awk si velocizza eccome e ti hanno gia` spiegato il perche`.
Se poi usi anche wc, migliora ancora.
Ho fatto una prova leggendo da un file con 1000 righe.
 
Usando due cicli come facevo io, uno per stabilire la lunghezza ed uno
per stampare a destra, impiega ~ 0.06 secondi sul mio PC
 
Se sostituisco il while con wc il tempo si dimezza a ~ 0.03 sec.
 
Se poi uso la tua soluzione il tempo di esecuzione si attesta attorno a
0.01 secondi: bella differenza!
 
Emh, ora si tratterebbe di stampare la tabella con tutte e tre le
colonne ..
 
 
 
 
 

 la tabella con tutte e tre le colonne
 
 
Se vedi l'altro mio post più recente
vedrai lo script intero che porta alla
stampa delle tre colonne nel formato
desiderato.
 
Il problema dell'allineamento a destra
in realtà si risolve da solo applicando
il formato delle tre colonne preimpostato
e al quale è necessario che io faccia
riferimento.
Per ulteriori dettagli, leggi l'altro
ramo del tread.
 
In sintesi, l'allineamento a destra è
un problema fine a se stesso.
La soluzione che ho proposto in questo
ramo del tread  serviva solo per completare
il discorso "allineamento a destra".
 
Il problema principale invece era la
necessità di risputare le tre colonne
nel foramto uguale al file di partenza,
al quale i dati estrapolati vanno accodati.
Spero di essermi spiegato...
 
Ad ogni modo ringrazio tutti per la
disponibilità!
Un saluto.

 Il codice precedente che hai abbozzato è Perl o Awk?
 
Awk
 
Enrico


Grazie, leggendo qualcosa sul perl me ne stavo
accorgendo...

 Il codice precedente che hai abbozzato è Perl o Awk?
 
 
AWK mi pare ovvio. A parte che già ti hanno rispostoi
 
Non appena posso posto un esempio  di codice PERL