Newsgroup: it.comp.os.linux.iniziare
Titolo: bash: trasformare comando in stringa

my_confirm_command() {
     # parametri
     local COMMAND=("$@")
 
     local commandString=$(echo "${COMMAND[@]}")
	local answer; read -p "Eseguire il comando '$commandString'? (s/n)" answer
 
	[ "$answer" != "s" ] && return
 
     "${COMMAND[@]}"
}
 
my_confirm_command \
	echo p1 "p 2"
 
Output:
 
Eseguire il comando 'echo p1 p 2'? (s/n)n
 
Non so se avete notato che così com'è, la domanda che viene fatta 
contiene un'imperfezione che conviene evitare...
 
Cioè sarebbe meglio che *p 2* comparisse dentro gli apici originali, cioè:
 
Eseguire il comando 'echo p1 "p 2"'? (s/n)n
 
In modo da capire esattamente il comando che verrà eseguito.
 
Si può?


Prova a escapare le virgolette nel richiamo alla funzione:
 
my_confirm_command \
       echo p1 \"p 2\"
 
 
 
 
Salta fuori una roba del tipo:
---------------
$ ./commando.sh
Eseguire il comando 'echo p1 "p 2"'? (s/n)s
p1 "p 2"
--------

Il 13/09/20 16:12, Joe ha scritto:

$ echo OK > "a b"
 
# APICI: NO
# ERRORI: NO
$ my_confirm_command cat "a b"
Eseguire il comando 'cat a b'? (s/n)s
OK
 
# APICI: SI
# ERRORI: SI
$ my_confirm_command cat \"a b\"
Eseguire il comando 'cat "a b"'? (s/n)s
cat: '"a': File o directory non esistente
cat: 'b"': File o directory non esistente
 
Insomma...

 Insomma...
 
Già hai ragione.. se hai bisogno di gestire un argomento contenente
spazio e allo stesso tempo farlo digerire a bash come nell’esempio
di un file esistente nominato con spazio, l’escaping delle virgolette
non va bene.
D’altra parte è qffettivamente un po’ un casino perché se bash vede
le virgolette le interpreta come espansione e le elimina, quindi
quando devi elaborare l’input le virgolette non le hai più, come hai
già osservato.
Nel tuo esempio sopra però si salva un aspetto, ovvero gli elementi
dell’array di input:
 
cat "a b"
 
È un array di due elementi, grazie alle virgolette:
 
COMMAND[0] = cat
COMMAND[1] = a b
 
Occhio che COMMAND[1] è proprio
 
a b
 
Senza virgolette!
A dimostrazione che bash le interpreta correttamente aggregando
i due caratteri anche se separati da spazio, ma poi butta via le
virgolette. Quindi non puoi più riutilizzarle nella successiva
elaborazione per stampare a video il comando immesso.
 
Puoi però riaggiungerle in fase di stampa quando richiedi la conferma.
 
echo \"COMMAND[1]\"
 
Così stamperai:
 
"a b"
 
Mettndo insieme le osservazioni converrebbe fare i seguenti passaggi:
 
- popolo l’array COMMAND come hai già fatto: ("$@")
 
- poi ad esempio creo un secondo array i cui elementi sono:
a) uguali a quelli di COMMAND se l’elemento è il primo (ovvero ha indice
zero) oppure se non contiene spazi.
b) uguali a quelli di COMMAND, ma messi tra virgolette (lo fai coi
backslash vedi sopra), nel caso in cui l’elemento in questione ha indice
maggiore di zero e allo stesso tempo contiene uno spazio.
 
Lo fai con un ciclo for su ogni elemento dell’array COMMAND.
 
A quel punto avrai due array distinti:
 
COMMAND lo userai per eseguire il comando efettivamente, in quanto
le vrigolette preinterpretate da bash e buttate via, assicurano che
il comando poi funzioni.
 
L’altro gemello “diverso” che potresti chiamare PRINTCMD ad esempio,
lo userai nella stampa a video del comando digitato del quale chiedi
conferma di esecuzione all’utente.
 
Il tutto è un po’ più complicato di come ci si aspetterebbe, ma
credo che bash e le virgolette quando si vuol fare una cosa come
quella che richiedi facciano un po’ a pugni.

 quella che richiedi facciano un po’ a pugni.
 
 
$ tree
.
├── a b
└── commando.sh
 
0 directories, 2 files
 
$ cat a\ b
OK
 
 
 
$ cat commando.sh
#!/bin/bash
my_confirm_command() {
        local COMMAND=("$@") PRINTCMD=("") answer
        for arg in "${COMMAND[@]}"; do
            if [[ $arg =~ \  ]]; then
                    PRINTCMD+=(\"$arg\")
            else
                    PRINTCMD+=($arg)
            fi
        done
        echo "Eseguire il comando ${PRINTCMD[@]}? (s/n)"
        read answer
        [ "$answer" != "s" ] && return
        "${COMMAND[@]}"
}
my_confirm_command \
        cat "a b"
 
 
 
$ ./commando.sh
Eseguire il comando  cat "a b"? (s/n)
s
OK
 
$ ./commando.sh
Eseguire il comando  cat "a b"? (s/n)
n
 
 
 
 
Questa è un'idea: ovvero popolare il nuovo array
"di stampa" dentro il ciclo for. Ci sta, visto che
un comando non avrà un miliardo di argomenti.
Tuttavia, un'alternativa potrebbe essere:
 
- copio COMMAND su PRINTCMD
- cerco tra gli elementi di PRINTCMD quelli che contengono uno spazio
- li sostituisco con lo stesso elemento ma "quotato"
 
Potrebbe essere più sbrigativo come modo di operare,
ma così su due piedi non lo saprei fare. Non so come
il "Parameter Expansion" della Bash acctta la sostituzione
di un pattern con "se stesso" modificato. Per capirci, il
corrispondente di sed \( ... \) :
 
sed 's/^\(.* \.*\)$/\"\1\"/'
 
O qualcosa di simile insomma...

Il 14/09/20 18:53, Joe ha scritto:

Grazie dei suggerimenti anche se sono un po' macchinosi :)

 Grazie dei suggerimenti anche se sono un po' macchinosi :)
 
Caro alex, le cose macchinose sono ben altre, credimi! :D :D
 
Qua il concetto è banale: siccome bash disgraziatamente
interpreta le virgolette come un comando di espansione,
mentre tu vuoi utilizzarle come carattere da stampare a
video, o passi a qualche altro linguaggio, oppure devi
aggirare questo stato di fatto in qualche modo.
Quello che ti ho indicato qui sopra prevede dal punto
di vista del codice, 6 righe in più, un array di appoggio
e una variabile. Ripeto, la roba macchinosa è ben diversa,
ad ogni modo come ho accennato può darsi ci siano modi più
furbi e/o più eleganti per raggiungere lo stesso scopo.
 
In particolare usando una versione recente di bash, mi
pare 4.4 avevo letto che hanno introdotto un’estensione
al parameter expansion, appositamente per quotare gli
elementi dell’array.
 
https://www.gnu.org/software/bash/manual/html_node/Shell-Parameter-Expansion.html
 
---------------------
${parameter@operator}
 
    The expansion is either a transformation of the value of parameter
    or information about parameter itself, depending on the value of
    operator. Each operator is a single letter:
 
        Q
 
	        The expansion is a string that is the value of parameter
		quoted in a format that can be reused as input. 
---------------------------------------------------------------
Non è una soluzione molto portabile però dopotutto.
 
 
 
Il limite nella soluzione che ti avevo proposto, così al
volo è come ho già detto, che se il comando avesse 100 o
1000 o più argomenti, bash dovrebbe fare la ricerca e
testare se ognuno degli N elementi contiene uno spazio.
Questo sarebbe un grosso inconveniente in termini di
efficienza. D’altra parte bash quando deve lavorare in
modo sequenziale, tipo con cicli for come quello sopra,
non va bene per moli di dati corpose.
Sta cosa la vedi ad esempio se prendessi un file con un
elenco di elementi dei quali alcuni contengono uno o più
spazi. Puoi porvare a testare il file riga per riga con
un "while read do... done < file" opure usare sed o awk
o perl e cronometrare, vedresti che bash è  palesemente
molto più lenta.
Ovviamente se ti serve sta roba per testare un comando
che potrà avere ragionevolmente solo 4 o 5 argomenti, o
comunque metti pure siano anche una ventina al massimo,
allora il problema di efficienza non si pone proprio e
ci possiamo più che accontentare.
 
Un’alternativa abbastanza brutale potrebbe essere:
 
1- stampo l’array COMMAND su un file
 
$ printf '%s\n' "${COMMAND[@]}" > command-elements
 
2- aggiungo il quoting degli elementi con spazio usando
   sed o awk o similari
 
3- carico l'array PRINTCMD dal file modificato:
   gli elementi con spazio avranno ora le virgolette
   incluse. Per lo scopo si può usare il builtin bash
   mapfile o readarray.
 
4- uso PRINTCMD per la stampa e COMMAND per l'esecuzione
   come già fatto anche nell'altro esempio
 
Al posto di usare un file temporaneo di appoggio, si
può anche ricorrere alla "Process substitution" e alla
pipe, decisamente più elegante.
Praticamente scrivo COMMAND e filtro l'output con sed
impostando le virgolette, ma questa roba in uscita la
giro subito a mapfile che la carica nell'array PRINTCMD,
così ci si risparmia il file temporaneo.
 
Va be' via traduco in script:
-----------
#!/bin/bash
 
alt_confirm_command() {
        local COMMAND=("$@") PRINTCMD=("") answer
        mapfile -t PRINTCMD < <( printf '%s\n' "${COMMAND[@]}" | sed '/ /s/.*/\"&\"/' )
        read -r -p "Comando   --->|${PRINTCMD[*]}|<---         Confermi lancio? (s/n)" answer
        [ "$answer" != "s" ] && return || "${COMMAND[@]}"
}
 
alt_confirm_command \
        cat "a b"
-----------------
 
In questo modo ti eviti il ciclo for su ogni elemento.
Aggiungo che visto qualche problema con virgolette ecc,
ho trovato utile per capire dove sta l'errore questo:
 
https://www.shellcheck.net/
 
 
Incolli lo script lì e lui ti dice cosa hai sbagliato e
suggerisce anche la correzione.
Alla fine questa versione sì che può sembrare più macchinosa
rispetto al ciclo for della precedente, ma presenta credo
qualche vantaggio come ho spiegato.

Il 15/09/20 15:08, Joe ha scritto:

Grazie ancora, appena ho tempo vedrò se è il caso di adottare uno dei 
tuoi suggerimenti.

In date: Sun, 13 Sep 2020 11:41:33 on group: it.comp.os.linux.iniziare,

Vedi se questo ti può aiutare (debbo fare appunto una cosa simile alla tua).
 
 
-------------------------------------------------------
 
./test_debug 
Comando :   a="geronimo" ; b="asso" ; c="7778" ; echo "$a $b"  "$c"
 
Esegui il comando: a="geronimo" ; b="asso" ; c="7778" ; echo "$a $b"  "$c" ?   (s/n)s
 
geronimo asso 7778
 
 
------------------------------------------------------
 
./test_debug 
Comando :   echo p1 "p 2"
 
Esegui il comando: echo p1 "p 2" ?   (s/n)s
 
p1 p 2
 
 
 
 
 
------------------------------------------------------
cat test_debug 
#!/bin/bash
 
read -p "Comando :   " g
printf -v string2 '%s' "$g"
echo
printf '%s%s%s' 'Esegui il comando: ' "$string2" ' ?   (s/n)'
read -t 10 T
if [ "$T" == "s" ]; then
    echo
    eval "$string2"
    echo
    echo
fi
------------------------------------------------------
 
 
 
 
 
Se al posto del primo "read" metti la tua stringa esattamente come sotto, tra apici semplici:
 
g='p1 "p 2"'
 
 
./test_debug 
Esegui il comando: echo p1 "p 2" ?   (s/n)s
 
p1 p 2
 
 
 
-- 
--- Terminate 1.41k
 * Origin: Broken condom error: (R)eplace, (I)gnore, (O)ccazzo!!
(2:332/507.12)

 Insomma...
 
Non devi eseguire il comando direttamente,
devi darlo di nuovo in pasto a bash.
 
Cioe`:
 
echo $COMANDO | bash
 
In questo caso, vi e` una buona probabilita`
che gli apici, con o senza escape, siano
consistenti tra la stampa e l'esecuzione.
 
bye,
 
-- 
 
piergiorgio

Il 19/09/20 16:02, Piergiorgio Sartor ha scritto:

$ cat test
echo "$@"
echo "$@" | bash
 
$ bash test cat 'a b'
cat a b
cat: a: File o directory non esistente
cat: b: File o directory non esistente
 
Bingo :D

Il 19/09/20 13:56, BIG Umberto ha scritto:

$ cat test
my_confirm_command() {
     # parametri
     local COMMAND=("$@")
 
     local commandString
     printf -v commandString '%s' "$COMMAND"
	local answer; read -p "Eseguire il comando '$commandString'? (s/n)" answer
 
	[ "$answer" != "s" ] && return
 
     "${COMMAND[@]}"
}
my_confirm_command cat 'a b'
my_confirm_command cat "a b"
 
$ bash test
Eseguire il comando 'cat'? (s/n)
Eseguire il comando 'cat'? (s/n)
 
Un po' deludente...


Non hai capito nulla di come funziona bash.
 
echo "ls \"a b\"" --> cosa stampa?
echo "ls \"a b\"" | bash --> cosa fa?
 
Tutti i caratteri che bash interpreta devono
essere messi in "escape", cioe "\".
Questi escape sono validi sia per echo che
per bash, quindi, se si fanno le cose a dovere,
le due situazioni sono identiche.
 
Se, pero`, si fa:
 
echo 'ls \"a b\"'
 
Questo non funziona, perche` "'" blocca
l'espansione di bash (e` testuale).
 
Ovviamente questo funziona anche:
 
echo ls \"a b\"
echo ls \"a b\" | bash
 
bye,
 
-- 
 
piergiorgio

 Un po' deludente...
 
 
Senti un po'! Se HO MESSO la domanda sotto 'printf' su una riga, e la lettura
della conferma (il read) su un'altra è perchè va fatto così!
 
Volevo scusarmi perchè nel post precedente dell'altro theared la sign, messa in
automatico da un programma poteva essere offensiva, me ne sono accorto adesso
aggiornando i messaggi, ora mi accorgo che ti è involontariamente meritata!
 
 
 
COROLLARIO
Ho fatto per oltre 30 anni assistenza tecnica ai clienti, mi sono capitate
persone di tutti i tipi, anche 'elettricisti' che mi chiedevano come mettere i
puntali del tester per misurare una tensione di 24 volt continui e non ho mai
perso la pazienza, ma ora tu ci sei riuscito!
 
 
 
 
 
-- 
Il problema nell'essere immortali è che non si riesce ad avere
amicizie durature.  Muoiono tutti prima!

Il 20/09/20 12:26, Piergiorgio Sartor ha scritto:

Hai perfettamente ragione.
E cmq per ora ci rinuncio al mio obiettivo iniziale: anche se la 
*preview* (contenuta nella domanda di conferma) non è completamente 
fedele al comando che verrà eseguito, pazienza.
bash non è proprio il massimo della comodità su certe cose: è 
impossibile ricordarsi sempre di escapare, ricordari questo e quello, 
prevedere tutti i comportamenti imprevisti :D


In realtà la mia soluzione prtava l’escaping dentro la funzione,
mi pare di ricordare attraverso il printf. Questo ti consente di
non doverti ricordare nulla.
Sul fatto che non sia il massimo della comodità per certe cose
siamo d’accordo, potrebbe essere che per certe cose ci siano
strumenti più adeguati o che quelle “certe cose” siano inutili
ruote reinventate.
Didatticamente per carità, ci sta tutto, per il resto invece,
quando serve davvero qualcosa di operativo bisogna ben capire
quale sia l’esigenza e a valle di questo, scegliere lo strumento
più adeguato.
La mia soluzione comunque sembrerebbe funzionare, e se ti serve
davvero uno script, non ti può fregar di meno quanto sia complessa
o poco elegante ecc ecc: lo script una volta scritto è una scatola
chiusa nella fase di utilizzo, lo richiami e stop.
Idem a livello di codice: la funzione una volta scritta la richiami
nello script dove ti serve e via...