Archivi Categorie: bash

mou: non funziona, non so il perché e allora la faccio funzionare; senza sapere il perché

art-ma-3

Tutto parte da questo tweet che rimanda a questo post.

Uh! semplice, non so se possa servire ma è sexy lo provo:

$ history | awk '{print $2}' | awk 'BEGIN {FS="|"}{print $1}' | sort | uniq -c | sort -n | tail | sort -nr
    103 spo
    100 mtw
     94 stw
     53 history
     47 _j
     46 _e
     42 sx
     39 cd
     33 bash
     29 gen
$

OK! ma mica devo scrivere ogni volta tutto questo; lo meto in uno script ed ecco:

$ cat m0
history | awk '{print $2}' | awk 'BEGIN {FS="|"}{print $1}' | sort | uniq -c | sort -n | tail | sort -nr
$ bash m0
$

OOPS! NO! non va.

Qui –no, non so spiegarmi il perché– ho dato la colpa a awk e l’ho sostituito con sed. sed è più semplice, o ci sono più abituato, anche se quando sono finito su Unix mi sono procurato subito le fotocopie della shell (di SisV), di vi (un articolo scritto da Bill Joy per Hewlett-Packard) e awk (sì perché sembrava intrigante, assay.

La mia versione:

$ history | sed 's/[ 0-9]*//' | sed 's/[ ].*//' | sort | uniq -c | sort -n | tail | sort -nr
    103 spo
     98 mtw
     93 stw
     54 history
     47 _j
     46 _e
     42 sx
     39 cd
     34 bash
     30 gen
$

OK! sì lo so che si potrebbe approfittare che history scrive il nome del programma sepre in colonna 7 ma già vengo spesso accusato di fortranismo residuo…

Provo a scriptarlo e

$ cat m1
history | sed 's/[ 0-9]*//' | sed 's/[ ].*//' | sort | uniq -c | sort -n | tail | sort -nr
$ bash m1
$

NO! come prima. Ho sentito Stack Overflow,  googlato  duck-duck-go-ato; visto che anche altri chiede a riguardo per operazioni simili riguardanti history ma senza risultati. Allora ho cancellato tutto, in fondo a chi interessa una cosa così.

Poi capita che di notte, avete presente (sensa comparision (pron. sensa cumparisiun) dicono qui in Piemonte) Srinivasa Ramanujan e la dea Lakshmi di Namagiri? Ecco a me lo stesso ma seguo la Vera Fede e allora vengo ispirato da Sua Pastosità il Prodigioso FSM –sempre sia condito, RAmen– subito nella prima notte utile.

Fortuna che quando cancelli il ‘puter sa che gli umani a volte non sanno quello che fanno o poi cambiano idea e allora si può ripristinare il cancellato.

Se il problema è history lo si può tener fuori dallo script e eseguire con una pipe. (Nota OT, qui: non so mai che genere usare, mi manca il neutro). Lo script (mou-r) diventa:

#!/bin/bash
sed 's/[ 0-9]*//' | sed 's/[ ].*//' | sort | uniq -c | sort -n | tail | sort -nr

che abilitato e copiato in ~/bin produce

$ history | mou-r
    102 spo
     98 mtw
     93 stw
     57 history
     46 _j
     46 _e
     42 sx
     39 cd
     35 bash
     30 gen
$

OK, ma è troppo lungo, creo un alias

$ alias mou='history | mou-r'
$ mou
    102 spo
     98 mtw
     92 stw
     57 history
     46 _j
     46 _e
     42 sx
     39 cd
     35 bash
     30 gen
$

Perfetto. L’alias ovviamente va messo in ~/.bash_aliases (o qualche altro file eseguito al boot. Ah! mou sta per most used. Resta il mistero di history ma frankly, my dear, I don’t give a damn (cit.) 🧐

Gestire la clipboard

top4

A me nessuno dice mai niente ma indagando ontehtoobz ogni tanto trovo cose…
Come xclip, fa proprio quello che volevo da tanto tempo. A dirla tutta ne ho già parlato ma non in modo organico, cerco di rimediare.

Niente di meglio che un esempio per vedere cosa fa, con la teoria non sono tanto buono, ecco:

c0

e nella clipboard ho, vediamo con Crtl-V

prima riga
seconda
terza.

cioè l’output del comando precedente. Comodo, molto più comodo di selezionare il testo e copiarlo con Maiusc-Ctrl-C.

Tanto comodo (come si vedrà in un prossimo post (forse)) che ho creato lo script _k, abilitato e messo in ~/bin

#!/bin/bash
xclip -f -selection clipboard

Da testare:

c2

e nella clipboard ho:

3.142857142857143
3.141592653589793

Come Ctrl-V fa coppia con Ctrl-C creo _p che fa coppia di _k:

#!/bin/bash
xclip -o  -selection clipboard

lo abilito e sposto in ~/bin ed ecco:

c4

Per sapere tutto di xclip aprire questo link. Prossimamente risulterà meno oscuro come intendo utilizzare _k &co (probabilmente… forse…) 😐

Numeri troppo grandi o troppo piccoli – 1

Scrooge-McDuck-Money-Bin-1000x562

Alle volte capita di avere a che fare con numeri che sono non come quelli normali, immaginabili facilmente dagli umani (anche da me).

Escluderei subito Paperon de’ Paperoni che, secondo Don Rosa, possiede five multiplujillion, nine impossibidillion, seven fantasticatrillion dollars and sixteen cents e per Carl Banks il deposito (Bin) contiene three cubic acres of money e qui non è solo questione di ordini di grandezza ma peggio, roba che in confronto gli stringhisti sono sensati, per cui salto e ricomincio.

Prendiamo per esempio gli astronomi, loro trattano roba come:

Sono parenti stretti dei fisici, a occhio nudo non si riesce (almeno io non ne sono capace) a distinguere gli uni dagli altri. Ma anche loro hanno (danno) numeri che –per esempio:

  • Massa del protone: 1.67262171(29)e-27 kg ossia 0.938 GeV/c2;
  • L’elettrone è ancora più piccolo (ma siamo lì) invece il neutrino (uno a caso, non facciamo i difficili) andiamo su 0.05 eV/c2 o 8.913309e-38 kg.

 Senza senso  Per fortuna esistono i multipli e i sottomultipli delle unità di misura, almeno per SI, il Sistema internazionale di unità di misura.

Quindi usare SI, unità e multipli; poi quando servono se ne fanno altre, come già visto per il parsec. Per esempio i fisici usano il barn (b = 10e-28 m2). Umorismo qui: un fienile di quelle dimensioni non ha senso (tranne per i fisici, ovviamente, Marco lo usa a volte, anche inverso).

Senza arrivare a questi estremi anch’io (con l’aiuto di più amici, questi script sono nati come un gioco, facendo altro) sto tentando di addomesticarli per quanto possibile. Il lavoro non è concluso ma un paio di script consentono di visualizzare il numero normale in formato scientifico (2sc) e viceversa (2ns).

Rendere il numero in formato scientifico – 2sc

#!/bin/bash
N=$@
if [ -z $NFORM ]; then
    NFORM="%.4e"
fi
N=$(echo $N | sed 's/\./\,/g')
N=$(printf "$NFORM  " $N)
N=$(echo $N | sed 's/\,/\./g')
echo $N | xclip -f -selection clipboard

Il lavoro viene svolto da printf, on base al formato, %.ne dove n è un intero positivo non troppo grande; di deafault vale 4, cioè quattro cifre decimali ma è possibile cambiarlo facilmente senza modificare lo script (come vedremo tra breve).

C’è un problema, per alcuni di noi, quelli abituati a programmare: Bash vuole la virgola come simbolo di definizione per la parte decimale (per noi italiani) contrariamente all’abitudine e a (quasi) tutti i linguaggi di programmazione. Niente panico con sed si risolve prima di subito.

Visto che il risultato di queste trasformazioni è spesso da usare immediatamente entra in gioco xclip che inserisce nella clipboard il risultato visualizzato.

$ 2sc 9460700000000000
9.4607e+15
$ 2sc 30857000000000000
3.0857e+16
$ 2sc 149597870700
1.4960e+11

e, ovviamente

$ 2sc 9460700000000000 30857000000000000 49597870700
9.4607e+15 3.0857e+16 4.9598e+10

Il numero di cifre decimali è facilmente ridefinibile: se non va bene il alore 4 di default si può ridefinire la variabile d’ambiente NFORM, con

export NFORM='%.3e'

e si può verificare con

$ echo $NFORM
%.3e

e si avrà

$ 2sc 9460700000000000 30857000000000000 49597870700
9.461e+15 3.086e+16 4.960e+10

NFORM conserva il valore all’interno del terminale, chiudendolo diventa indefinita. È possibile annullarla (sdefinirla?, undefinirla?) con export NFORM=

Ma si può semplificare, con uno script (particolare, in qualche misura atipico), ecco nprec

#!/bin/bash
if [ -z $1 ]; then
    export NFORM=''
else
    export NFORM='%.'$1'e'
fi
echo $NFORM

e ottengo

$ source nprec 2
%.2e
$ echo $NFORM
%.2e
$ source nprec

$ echo $NFORM

$

La particolarità è che lo script va eseguito con source.

Da formato scientifico all’usuale – 2ns

#!/bin/bash
R=""
while [[ $# -gt 0 ]]; do
    N=$(echo $1 | sed -e 's/\./\,/')
    N=$(printf "%.30f" $N)
    ye=$(echo $N | grep -c "\,")
    if [[ $ye == 1 ]]; then
        N=$(echo $N | sed -e 's/[0]*$//')
        N=$(echo $N | sed -e 's/\,$//')
    fi
    N=$(echo $N | sed -e 's/\,/\./')
    R=$R' '$N
    shift
done
echo $R  | xclip -f -selection clipboard

La trasformazione della rappresentazione del numero è svolta da printf.

Notare l’if che controlla se il numero ha una parte decimale; se sì sed elimina gli 0 finali e se il piunto è finale anche questo viene eliminato. Queste operazioni richiedono di trattare un numero alla volta, sempre il primo ($1) perché l’ultima istruzione del loop while è shift che, appunto, elimina l’elemento appena trattato.

$ 2ns 9.4607e15
9460700000000000
$ 2ns 3.0857e16
30857000000000000
$ 2ns 1.49597870700e+11
149597870700

OK, funziona, con numeri non troppo grandi (sorry Mr Scrooge!)

$ 2ns 1.23e+25
12300000000000000000000000
$ 2ns 1.23e+29
123000000000000000002281701376
$ 2ns 1.23e+30
1229999999999999999988457275392
$ 2ns 1.23e+35
122999999999999999998102457678823424
$ 2ns 1.23e+40
12300000000000000000542639153683841941504

OOPS! la precisione, a un certo punto non sono più 0 tondi ma altre cifre 😡 Ma tanto questi numeri sono illeggibili (o meglio io non riesco a leggerli). Da fare (prossimamente?) per renderli più amichevoli: separare con spazi le cifre in gruppi di 3 come usano i commerciali, volendo anche con i punti proprio come fanno i Rag.

Il valore della funzione in Bash

PETSCIIBOTS-1

Gli script Bash sono comodi, versatili, utilissimi ma a volte… Esempio (puramente didascalico, non realistico, nèh!): il prodotto di due numeri, in Python posso scriverlo così (file per.py):

#:/usr/bin/python3

def per(na, nb):
    res = na * nb
    return res

# test

r1 = per(6, 7)
print(r1)

r2 = per(3.7, 7.3)
print(r2)

Un pythonista produrrebbe un codice più sintetico ma sono sul didattico. Eseguendolo ottengo

$ python3 per.py
42
27.01
$

OK, funziona sia con gli interi che con i reali. In Bash c’è l’istruzione return ma non è quello che serve: ritorna l’exit code della funzione, un intero compreso in [0..256]. In effetti in Bash l’equivalente di return degli usuali linguaggi non manca, bisogna ricorrere a una qualche rimedio, ce n’è almeno uno facilmente usabile: usare una variabile, viene vista globalmente, il codice diventa (file per.sh):

#/bin/bash

function per () {
    res=$(echo "$1 * $2" | bc -l)
}

# test

per 6 7
echo $res

per 3.7 7.3
echo $res

Eseguo:

$ bash per.sh
42
27.01
$

Credo serva qualche chiarimento.

  • la definizione do funzione inizia con function seguita da nome, coppia di parentesi () senza gli argomenti e graffa aperta {. Nel nostro caso function per () {. È però possibile omettere la parola function, la riga diventa: per () {;
  • gli argomenti all’interno della funzione vengono visti come $1, $2, ...;
  • nel caso in esame siamo costretti a usare bc perché vogliamo trattare (anche) numeri reali; inoltre il carattere * verrebbe interpretato da echo per cui occorre racchiudere l’espressione tra virgolette. Ma posso usare varianti, p.es: res=$(echo $1 "*" $2 | bc -l), res=$(echo $1 \* $2 | bc -l);
  • al posto di $(…) posso usare la vecchia sintassi, sconsigliata da POSIX: res=`echo $1 \* $2 | bc -l`;
  • la funzione viene chiamata con il nome seguito dai parametri separati da spazio, p.es: per 6 7;
  • la variabile usata per ritornare il valore della funzione, in questo caso res, è visibile al ritorno dalla funzione.

È possibile avere funzioni che ritornano più valori, ecco magmin.sh:

#/bin/bash

magmin () {
    st=$1
    res1=${st^^}
    res2=${st,,}
}

# test

magmin "Stringa MAG e min 💥"
echo $res1
echo $res2

che produce

$ bash magmin.sh
STRINGA MAG E MIN 💥
stringa mag e min 💥
$

Nota: si può evitare l’uso della variabile di supporto st, l’espressione res1=${1^^} funziona ma, secondo me, è meno chiara.

Quindi, in conclusione: è possibile usare funzioni in Bash come negli altri linguaggi di script (Python, JavaScript, …). Ma è pratico? Con tutte le alternative liberamente disponibili è da valutare caso per caso.

Messaggio colorato nel terminale – 1

kk2

Se un messaggio è importante deve attrarre l’attenzione. Argomento già trattato in passato ma questa è la versione 2018.

Ci sono due modi facili: un suono e un colore diverso dal solito.

Produrre un beep è semplice, così:

#!/bin/bash
echo -en "\a"

o così:

#!/usr/bin/python3
print("\a", end="")

Visualizzare il testo colorato è risultato immediato con Python, più impegnativo con Bash. I codici delle sequenze di escape ANSI si trovano (come farei senza la Wiki) qui: ANSI escape code – Colors.

La versione Python:

#!/usr/bin/python3

import sys

RED='\033[01;31m'
GREEN='\033[01;32m'
YELLOW='\033[01;33m'
BLUE='\033[01;34m'
ENDC='\033[00m'
BEEP='\a'

dis = str.join(' ', sys.argv[1:])

dis = dis.replace('%R', RED)
dis = dis.replace('%G', GREEN)
dis = dis.replace('%Y', YELLOW)
dis = dis.replace('%B', BLUE)
dis = dis.replace('%Z', ENDC)
dis = dis.replace('%_', BEEP)

print(dis)

 

I codici sono nella forma %L dove %L è una lettera (o anche più di una, o _ per il beep) che genera una sequenza improbabile: di solito % finisce la parola, terminando un numero.

Più impegnativo produrre l’equivalente Bash: abbiamo, PLG e io, provato con sed ma con pessimi risultati. Poi la dritta, da Stack Overflow ed ecco:

#!/bin/bash

st=$*
st=$(printf '%s' "$st" | sed -e 's/:R/\\033[01;31m/g')
st=$(printf '%s' "$st" | sed -e 's/:G/\\033[01;32m/g')
st=$(printf '%s' "$st" | sed -e 's/:Y/\\033[01;33m/g')
st=$(printf '%s' "$st" | sed -e 's/:B/\\033[01;34m/g')
st=$(printf '%s' "$st" | sed -e 's/:Z/\\033[00m/g')

echo -e $st

sh
Rispetto alla versione Python cambia il codice, uso : al posto di %. Probabilmente da uniformare. Prossimamente, forse, chissà 😐

Sostituire i TABs e i caratteri non-ASCII

A volte il testo preso dal Web o da altri posti ancora non è come lo vorremmo. Capita che contenga caratteri strani, per esempio TABs, caratteri Unicode (a volte sono OK, a volte no, per esempio nel codice apici e virgolette devono essere quelli classici anche se meno belli), λ (OK ma non nella REPL di Racket (sì, questo solo per me)). Ecco il terminale è uno di quelli che certe cose proprio non le accetta.

Vero che in (quasi) qualsiasi editor si può fare ma se del caso…

Comincio con TAB, serve come base poi generalizzabile (file stab):

#!/bin/bash

if [ -z $1 ]; then
  echo 'uso:' $0 '[sost-char] file'
  exit 2
elif [ -z $2 ]; then
  SOST=' '
  PROC=$1
else
  SOST=$1
  PROC=$2
fi

sed "s/\t/$SOST/g" $PROC

Lanciato senza opzioni esce con un messaggio:

È richiesto il nome del file preceduto opzionalmente con il carattere (o i caratteri) che sostituiranno il TAB.

OK? C’è (ancora, pare che adesso Windows 10 sia sulla buona strada) chi preferisce Python. Si può fare; anzi lo script risulta simile (stab.py):

#!/usr/bin/python3

import sys, re

if len(sys.argv) == 1:
  print ('uso:', sys.argv[0], '[sost-char] file')
  exit(2)
elif len(sys.argv) == 2:
  sost = ' '
  proc = sys.argv[1]
else:
  sost = sys.argv[1]
  proc = sys.argv[2]

with open(proc, 'r') as f:
  txt = f.readlines()

for st in txt:
  print(re.sub('\t', sost, st), end='')

È possibile evitare l’uso delle espressioni regolari (modulo re che ho preferito per uniformità con lo script seguente): basta sostiture l’ultimo ciclo for con

for st in txt:
  print(st.replace('\t', sost), end='')

Generalizzando arrivo ad andare alla ricerca di tutti i caratteri fuori dalla sequenza ASCII. Ho copiato in un file un paio di tweets (dimenticando di salvare il nome degli autori) contenenti citazioni quotate. Nel primo si usano le virgolette che si trovano nella stampa tedesca, nel secondo no, roba corrente ontehtoobz. Ho aggiunto un paio di emoji, ormai hanno invaso il Web. Poi passo a evidenziali:

Questo è lo script fuori.py:

#!/usr/bin/python3

import sys, re

if len(sys.argv) == 1:
  print ('uso:', sys.argv[0], '[sost-char] file')
  exit(2)
elif len(sys.argv) == 2:
  sost = '_'
  proc = sys.argv[1]
else:
  sost = sys.argv[1]
  proc = sys.argv[2]

with open(proc, 'r') as f:
  txt = f.readlines()

for st in txt:
  print(re.sub(r'[^\x00-\x7f]', sost, st), end='')

Procedo come per il TAB ma con sed ci sono problemi, dovuti alla tabella caratteri caricata se abbiamo capito bene. Ancora da indagare (e risolvere), materia per un prossimo post.

OK, 😁⭕

Bash, quant’è grande, quante cose no so

Ho già confessato più vole che io Julia Evans, b0rk, la lovvo, assay 😍 E se non fossi così timido (e vecchio) una dichiarazione come si usa sarebbe già partita da tempo (ma si usa via Twitter?).

Ho contagiato anche qualcuno dei miei tweeps, recentemente ne parlavo con un nerd, molto più bravo di me, ed ecco un riassunto di quanto ci siamo detti, relativamente a more bash tricks e less.

Cominico con less, per me è più facile. Quando ho iniziato a usare Unix non c’era e l’imprint di more dura tuttora. Ma –me lo dico spesso– devo aggiornarmi. Anche qui ci sono cose che non sapevo, per esempio l’opzione v che ti apre il documento che stai lessando nell’editor definito dalla fariabile d’ambiente $EDITOR: uh! la setto subito, per me export EDITOR=medit verso il fondo di ~/.bashrc.

Il post sui tricks (non so tradurlo in italiano: trucchi? i francesi usano astuces ma astuzie non mi sembra possa funzionare) è ancora più ispirativo.

<(...) confesso che non lo conoscevo; colpa mia, c’è nel manuale Bash Reference Manual, prima o poi lo devo leggere tutto.

3.5.6 Process Substitution

Process substitution is supported on systems that support named pipes (FIFOs) or the /dev/fd method of naming open files. It takes the form of

<(list)

or

>(list)

9.2 Bash History Builtins

Bash provides two builtin commands which manipulate the history list and history file.

fc

fc [-e ename] [-lnr] [first] [last]
fc -s [pat=rep] [command]

Vale quanto detto per $EDITOR ma per adesso mi sembra più semplice richiamare il comando precedente con il tasto freccia-su. E qui si potrebbe inserire tutta la tiritera della history. OT: come la mettiamo con l’apostrofo? la h è ‘taliana o inglisc?

Shellcheck l’avevo già sentita nominare ma mai affrontata. Provandola su qualche scripts ho visto che dovrei aggiungere una spruzzata di virgolette. Da approfondire, c’è il man che mi indica anche il sito (su GitHub, in questi giorni l’unico gioco in città).

I suggerimenti di Julia vanno spesso approfonditi, sono un punto di partenza ma chi ben comincia…

Una nota OT: l’immagine iniziale dei miei post non è mai pertinente; serve solo per Twitter, con un’immagine il post risulta più visibile.

Python o Bash per gli scripts?

Se devi ripetere tre (numero che può variare ma comunque non molto più grande di 3) volte la stessa sequenza di comandi conviene fare uno script. Nel mondo Linux (e Unix prima ancora) esistono diversi linguaggi di scripting, si può scegliere volta per volta il più adatto. Un caso simile è capitato proprio ieri, adesso racconto.

Mi serve un contatore per memorizzare successivamente log di elaborazioni. Ecco lo script Bash che ho prodotto:

x-num

#/bin/bash

FileName="$HOME/dep/elab-num"

if [ -e "$FileName" ]; then
  NUM=`cat $FileName`
else
  NUM=0
fi

NUM=$((NUM + 1))
echo $NUM > $FileName

echo $NUM # qui vanno i comandi veri

Il numero dell’ultima chiamata a x-num è memorizzato nel file ~/dep/elab-num. Lo script legge questo numero, lo incrementa di uno, aggiorna il file scrivendo il numero aggiornato e, in questo esempio, scrive sul terminale questo stesso numero. Nella versione vera qui vengono i comandi di elaborazione dei dati. Qualora il file di log non esista il numero dell’elaborazione viene assunto 0; quindi funziona anche in questo caso con indice 1.

Provo a testarlo:

OK, funziona.
Qui devo raccontare una cosa personale, mi è già capitata più volte, continua a capitare, anche ieri… Nello script non si può usare il carattwere ~ per indicare la home, bisogna usare $HOME, cosa che mi dimentico invariabilmente. Adesso che l’ho scritta chissà…

Ieri ero con un amico che è uscito con la solita domanda: “E per Windows come si fa?”

Bella domanda, io uso Python. E questo è l’equivalente Python del precedente

py-num.py

#!/usr/bin/python3

import os

home = os.getenv('HOME')

FileName = os.path.join(home, 'dep/elab-num')

if os.path.exists(FileName):
  with open (FileName, 'r') as f:
    num = int(f.readline())
else:
    num = 0

num += 1
with open (FileName, 'w') as f:
  print(num, file=f)

print(num)

Cancello il file del contatore per essere nelle stesse condizioni di prima ed ecco:

OK. I due scripts fanno esattamente la stessa cosa, possono essere usati in modo intecambiabile (con Linux).

Sono entrambi molto piccoli:

Quale preferire? Se è solo per me io sceglierei la versione Bash, ma sono gusti, l’abitudine (fin dagli anni ’80). La versione Python è però, previa modifica del nome del file, funzionante anche su Windows.

Probabilmente la variabile d’ambiente corrispodente a HOME credo sia HOMEPATH ma chi conosco opera differentemente impostando FileName = "c:/dep/elab-num.txt". Sì meglio specificare l’estensione, anche se di solito non si vede.

“Sai, queste cose si possono fare anche in Basic” (cit.). Certo 😋

🤩

Ricerca con grep, caso con più stringhe sulla stessa linea – 2

Quando ho scritto su grep e la ricerca di più parole o frasi sulla stessa riga ho scritto due cose questionabili; sono stato corretto subito e anche se non sono invidioso non ringrazio il correttore 😡 Non è vero, pesce di metà gennaio 😁 grazie 😁 al giovane collaboratore 💥 PLG 💥 che non blogga e non twitta 😁

La soluzione per lo script bash è quella che ho chiamato “perlosa”, -P, in locale con man grep o qui

-P, –perl-regexp
Interpret PATTERN as a Perl regular expression. This is highly experimental and grep -P may warn of unimplemented features.

Vale per quante siano le espressioni, da una all’infinito (anche se, obiettivamente, dai!).

Ecco lo script, non c’è bisogno di commenti vero? CMQ c’è un accumulatore, PCMD che viene costriuto con gli argomenti, l’ultimo escluso; questo viene poi assemblato con la testa PC e la coda PD che termina con l’ultimo argomento passato.

Per vedere come funziona viene visualizzato –per ora– il comando da passare a eval.

#!/bin/bash

PCMD=""

while [[ $# -gt 1 ]] ; do
  PCMD=$PCMD'(?=.*'$1')'
  shift
done

PC="grep -P '"
PD="' "$1

echo kwindy $PC$PCMD$PD
eval $PC$PCMD$PD

Usando il file gtest ottengo:

gtest

3 4 5
1 4 5 6 2 3
a b c d
1 4 5 6 2 3
2 3 4
6 5 4 2 1 3
xxx
qui non ci sono 1 2 3
qui sì 123654

OK, vista la differenza tra le due richieste?

Ma questa versione ha un grosso bug che lo rende inutilizzabile. Lo so che l’avete già visto ma lo dico lo stesso: interpreta l’ultimo argomento come il nome del file da cercare e solo quello. Quindi non posso dirgli di cercare in più files, eventualmente con l’uso di jollies (? e *).

La modifica richiesta è semplice, introdurre negli argomenti uno di separazione; potrebbe essere -f ma se si volesse cercare proprio quell’espressione? Il più semplice che non dovrebbe generare ambiguità che ho scelto è ^^^.

Lo script diventa quindi:

#!/bin/bash

PCMD=""
while [[ $1 != '^^^' ]] ; do
  PCMD=$PCMD'(?=.*'$1')'
  shift
done

shift # toglie ^^^
PC="grep -P '"
PD="' "$@
eval $PC$PCMD$PD

Posso testarlo per gli scripts in ~/bin che usano awk:

Il carattere # deve essere escapato altrimenti la shell lo interpreta come inizio di commento.

Come verifica posso usare shbp, descritto qui.

Sì, shbp si poteva costruire più semplicemente usando g+. In realtà l’dea di usare grep mi  ci è venuta durante lo sviluppo di shb e shbp 😉

Nota: sì, manca la gestione degli errori; lasciato come esercizio. E nuovamente h/t a PLG 💥

🤩

Cos’è? dov’è?

Un paio di note di programmazione Bash e dintorni; niente di speciale, niente che non si sappia ma c’è un motivo perso: lunedì (ormai non più l’ultimo, ho temporeggiato) me ne serviva un pezzo, ricordavo di averlo fatto ma non riuscivo a trovarlo. Forse se lo metto nel blog… 😋

Quelli come me che installano roba per provarla e non la usano subito e poi —uh! già detto nella frase prima.

Se ci si accorge di usare tre volte (numero che può variare tra i nerds, ma comunque non tanto più grande di tre) un comando composto da più parole probabilmente ti conviene fare uno script. Io lo faccio, di solito.

which
Il primo problema è che la prima riga dello script –la shebang– deve il nome dell’eseguibile, assoluto, completo di tutta la path. Beh, questo è semplice, per esempio:

alias
A volte invece dello script conviene l’alias, per esempio per la REPL di IPython3 io ho:

OK, fin qui niente di nuovo. Ma c’è un utility che può servire, ecco

file
file fa tante cose, vedi man in locale o qui.

Funziona anche quando non penseresti, per esempio

Non è come potrebbe pensare l’utente Windows, non centra l’estensione:

Con ‘opzione -i la risposta è diversa:

E con gli scripts? Eccone uno classico:

hw.sh

#!/bin/bash
echo "Hello World!"

file mi dice

Abilitando lo script il risultato non cambia

Ci siamo quasi, torno a which:

Non lo vede, ma vede altri scripts, per esempio

Ah! funziona solo per le directories elencate nella path.

A ottobre (ahemmm… non l’ultimo) avevo unito which e file, così

tyk

#!/bin/bash

if [[ $# == 0 ]]; then
    exit 2
fi

N=$1
file $(which $N)

OK, ritrovato; ma non è quello che volevo.

alias
Prima accennavo agli aliases, ecco, potrebbe essere più interessante, per esempio

Si può scriptare? uhmmmm…

tyka

#!/bin/bash -i

if [[ $# == 0 ]]; then
    exit 2
fi

AL=$1
r1=$(alias $AL)
r2=$(echo $r1 | sed "s/^.*='//;s/'$//")
which $r2

Notare l’opzione -i nella shebang.

Finora ho trattato script interpretati. Cosa capita se creo l’eseguibile compilando? ecco qualche esempio.

hw.c

#include

void main()
{
    printf("Hello World!\n");
}

hw.nl

(println "Hello World!")
(exit)

hw.rkt

#lang racket/base

(println "Hello World!")

hw.hs

main :: IO ()
main = putStrLn "Hello, World!"

Uh! ‘na cosa che mi ricorda dello scorso millennio: qualcuno degli eseguibili ha informazioni per il debug. Naturalmente la dimensione degli eseguibili ottenuti varia tantissimo

Esiste il comando strip ma serve a poco; ormai lo spazio su disco non è più un problema.

OK, arrivo al punto. Come faccio a cercare eseguibili e scipts con certe caratteristiche?

Distinguere gli script dagli eseguibili è immediato, via file -i

Diventa allora (kwasy) immediato visualizzare la ricerca:

shb
Nota:
corretto in [[ $# -gt 0 ]] (era [[ $# -gt 1 ]])

#!/bin/bash

while [[ $# -gt 0 ]] ; do
  T=$(file -i $1)
  B=$(echo $T | grep "binary")
  if [ ${#B} -gt 0 ]; then
    echo $T     #  binario
  else
    echo $1":" $(head -1 $1) #  testo
  fi
  shift
done

sì ho parecchi comandi che iniziano con _, uno dei quali compilato _u.

Con qualche modifica posso cercare gli script di un determinato linguaggio:

shbp
Corretto l’indice come indicato per shb

#!/bin/bash

P=$1
shift

while [[ $# -gt 0 ]] ; do
  T=$(file -i $1)
  B=$(echo $T | grep "binary")
  if [ ${#B} -eq 0 ]; then
    SB=$(head -1 $1)
    OK=$(echo $SB | grep $P)
    if [ ${#OK} -gt 0 ]; then
      echo $1":" $SB
    fi
  fi
  shift
done

Ed ecco la versione per i compilati:

bex
Corretto l’indice come indicato per shb

#!/bin/bash

while [[ $# -gt 0 ]] ; do
  T=$(file -i $1)
  B=$(echo $T | grep "binary")
  if [ ${#B} -gt 0 ]; then
    echo $1
  fi
  shift
done

OK, mi resta da chmodarne un po’, metterne un po’ in ~/bin e fare pulizia. Prossimamente… Forse… 😋

🤩