Come posso generare argomenti per un altro comando tramite la sostituzione dei comandi


11

A seguito di: comportamento imprevisto nella sostituzione del comando shell

Ho un comando che può prendere un enorme elenco di argomenti, alcuni dei quali possono legittimamente contenere spazi (e probabilmente altre cose)

Ho scritto uno script che può generare questi argomenti per me, con virgolette, ma devo copiare e incollare l'output, ad es

./somecommand
<output on stdout with quoting>
./othercommand some_args <output from above>

Ho provato a semplificare questo semplicemente

./othercommand $(./somecommand)

e si è imbattuto nel comportamento imprevisto menzionato nella domanda sopra. La domanda è: la sostituzione dei comandi può essere utilizzata in modo affidabile per generare gli argomenti, othercommanddato che alcuni argomenti richiedono la citazione e questo non può essere modificato?


Dipende da cosa intendi per "affidabile". Se vuoi che il prossimo comando prenda l'output esattamente come appare sullo schermo e applichi le regole della shell ad esso, allora forse evalpotrebbe essere usato, ma generalmente non è raccomandato. xargsè qualcosa da considerare anche
Sergiy Kolodyazhnyy il

Vorrei (mi aspetto) che l'output somecommandsubisse un normale analisi della shell
user1207217

Come ho detto nella mia risposta, utilizzare qualche altro personaggio per la divisione di campo (come :) ... partendo dal presupposto che il carattere in modo affidabile , non essere in uscita.
Olorin

Ma non è proprio vero perché non obbedisce alle regole di quotazione, ecco di cosa si tratta
user1207217

2
Potresti pubblicare un esempio del mondo reale? Voglio dire, l'output effettivo del primo comando e il modo in cui vuoi che interagisca con il secondo comando.
nxnev,

Risposte:


10

Ho scritto una sceneggiatura che può generare questi argomenti per me, con virgolette

Se l'output è quotato correttamente per la shell e ti fidi dell'output , puoi eseguirlo eval.

Supponendo che tu abbia una shell che supporti gli array, sarebbe meglio usarne uno per memorizzare gli argomenti che ottieni.

Se ./gen_args.shproduce un output simile 'foo bar' '*' asdf, allora potremmo eseguire eval "args=( $(./gen_args.sh) )"per popolare un array chiamato argscon i risultati. Questo sarebbe i tre elementi foo bar, *, asdf.

Possiamo usare "${args[@]}"come al solito per espandere singolarmente gli elementi dell'array:

$ eval "args=( $(./gen_args.sh) )"
$ for var in "${args[@]}"; do printf ":%s:\n" "$var"; done
:foo bar:
:*:
:asdf:

(Notare le virgolette. Si "${array[@]}"espande a tutti gli elementi come argomenti distinti non modificati. Senza virgolette gli elementi dell'array sono soggetti alla suddivisione delle parole. Vedere ad esempio la pagina Array su BashGuide .)

Tuttavia , evaleseguirà felicemente qualsiasi sostituzione della shell, quindi $HOMEnell'output si espanderà nella directory home e una sostituzione di comando eseguirà effettivamente un comando nella shell in esecuzione eval. Un output di "$(date >&2)"creerebbe un singolo elemento array vuoto e stamperebbe la data corrente su stdout. Questa è una preoccupazione se si gen_args.shottengono i dati da una fonte non attendibile, come un altro host in rete, nomi di file creati da altri utenti. L'output potrebbe includere comandi arbitrari. (Se di per get_args.shsé era dannoso, non avrebbe bisogno di emettere nulla, poteva semplicemente eseguire direttamente i comandi dannosi.)


Un'alternativa alla quotazione della shell, che è difficile da analizzare senza valutazione, sarebbe quella di usare qualche altro personaggio come separatore nell'output del tuo script. Dovresti sceglierne uno che non è necessario negli argomenti reali.

Scegliamo #e abbiamo l'output dello script foo bar#*#asdf. Ora possiamo usare l' espansione dei comandi non quotata per dividere l'output del comando negli argomenti.

$ IFS='#'                          # split on '#' signs
$ set -f                           # disable globbing
$ args=( $( ./gen_args3.sh ) )     # assign the values to the arrayfor var in "${args[@]}"; do printf ":%s:\n" "$var"; done
:foo bar:
:*:
:asdf:

Dovrai tornare IFSindietro in seguito se dipendi dalla suddivisione delle parole altrove nello script ( unset IFSdovrebbe funzionare per renderlo predefinito) e utilizzare anche set +fse vuoi usare il globbing in seguito.

Se non stai usando Bash o qualche altra shell che ha array, puoi usare i parametri posizionali per quello. Sostituisci args=( $(...) )con set -- $(./gen_args.sh)e usa "$@"invece di "${args[@]}"allora. (Anche qui hai bisogno di virgolette "$@", altrimenti i parametri posizionali sono soggetti a divisione di parole.)


Il meglio di entrambi i mondi!
Olorin,

Aggiungeresti un'osservazione che mostra l'importanza di citare ${args[@]}- altrimenti non avrebbe funzionato per me
user1207217

@ user1207217, sì, hai ragione. È la stessa cosa con le matrici e "${array[@]}"con "$@". Entrambi devono essere citati, altrimenti la suddivisione in parole spezza gli elementi dell'array in parti.
ilkkachu,

6

Il problema è che una volta che lo somecommandscript emette le opzioni per othercommand, le opzioni sono in realtà solo testo e in balia dell'analisi standard della shell (influenzata da qualunque cosa $IFSaccada e quali opzioni della shell sono in effetti, cosa che nel caso generale non avere il controllo).

Invece di utilizzare somecommandper generare le opzioni, sarebbe più facile, più sicuro e più robusto usarlo per chiamare othercommand . La somecommandsceneggiatura sarebbe allora uno script wrapper attorno othercommandal posto di una sorta di script di aiuto che si dovrebbe ricordarsi di chiamare in qualche modo speciale come parte della linea di comando di otherscript. Gli script Wrapper sono un modo molto comune di fornire uno strumento che chiama semplicemente qualche altro strumento simile con un altro set di opzioni (basta controllare con filequali comandi /usr/binsono in realtà wrapper di script shell).

In bash, ksho zsh, potresti facilmente utilizzare uno script wrapper che utilizza un array per contenere le singole opzioni in questo othercommandmodo:

options=( "hi there" "nice weather" "here's a star" "*" )
options+=( "bonus bumblebee!" )  # add additional option

Quindi chiama othercommand(sempre all'interno dello script wrapper):

othercommand "${options[@]}"

L'espansione di "${options[@]}"garantirebbe che ogni elemento optionsdell'array sia citato individualmente e presentato othercommandcome argomenti separati.

L'utente del wrapper sarebbe ignaro del fatto che stia effettivamente chiamando othercommand, qualcosa che non sarebbe vero se lo script generasse invece le opzioni della riga di comando othercommandcome output.

In /bin/sh, utilizzare $@per contenere le opzioni:

set -- "hi there" "nice weather" "here's a star" "*"
set -- "$@" "bonus bumblebee!"  # add additional option

othercommand "$@"

( setÈ il comando utilizzato per impostare i parametri posizionali $1, $2, $3ecc Questi sono ciò che costituisce la matrice $@in una shell standard POSIX. L'iniziale --è per segnalare a setche non ci sono opzioni disponibili, solo argomenti. Il --è realmente necessaria solo se la il primo valore sembra essere qualcosa che inizia con -).

Si noti che sono le doppie virgolette in giro $@e ${options[@]}ciò assicura che gli elementi non siano suddivisi in parole singolarmente (e il nome del file sia alterato).


potresti spiegarmi set --?
user1207217

@ user1207217 Aggiunta spiegazione per rispondere.
Kusalananda

4

Se l' somecommandoutput presenta una sintassi della shell affidabile, è possibile utilizzare eval:

$ eval sh test.sh $(echo '"hello " "hi and bye"')
hello 
hi and bye

Ma devi essere sicuro che l'output abbia virgolette valide e simili, altrimenti potresti finire con l'esecuzione di comandi anche fuori dallo script:

$ cat test.sh 
for var in "$@"
do
    echo "|$var|"
done
$ ls
bar  baz  test.sh
$ eval sh test.sh $(echo '"hello " "hi and bye"; echo rm *')
|hello |
|hi and bye|
rm bar baz test.sh

Si noti che echo rm bar baz test.shnon è stato passato allo script (a causa del ;) ed è stato eseguito come comando separato. Ho aggiunto il |giro $varper evidenziarlo.


In generale, a meno che non ci si possa fidare completamente dell'output di somecommand, non è possibile utilizzare in modo affidabile il suo output per creare una stringa di comando.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.