Quando è il momento giusto (e l'ora sbagliata) di usare i backtick?

StackOverflow https://stackoverflow.com/questions/115809

  •  02-07-2019
  •  | 
  •  

Domanda

Molti programmatori principianti scrivono codice in questo modo:

sub copy_file ($) {
  my $from = shift;
  my $to = shift;

  `cp $from $to`;
}

È male, e perché? I bastoncini dovrebbero mai essere usati? In tal caso, come?

È stato utile?

Soluzione

Alcune persone hanno già detto che dovresti usare i backtick solo quando:

  • Devi catturare (o sopprimere) l'output.
  • Non esiste alcuna funzione integrata o modulo Perl per svolgere la stessa attività, oppure si ha una buona ragione per non utilizzare il modulo o incorporato.
  • Disinfetta l'input.
  • Controlli il valore restituito.

Sfortunatamente, cose come la verifica del valore di ritorno correttamente possono essere piuttosto impegnative. È morto per un segnale? Ha funzionato fino al completamento, ma ha restituito uno stato di uscita divertente? I modi standard di provare a interpretare $? sono semplicemente terribili.

Consiglio di utilizzare IPC :: System :: Simple funzioni capture () e system () del modulo piuttosto che backtick. La funzione capture () funziona esattamente come i backtick, tranne che:

  • Fornisce una diagnostica dettagliata se il comando non si avvia, viene interrotto da un segnale o restituisce un valore di uscita imprevisto.
  • Fornisce una diagnostica dettagliata se vengono trasmessi dati contaminati.
  • Fornisce un meccanismo semplice per specificare valori di uscita accettabili.
  • Ti permette di chiamare backtick senza la shell, se vuoi.
  • Fornisce meccanismi affidabili per evitare la shell, anche se si utilizza un singolo argomento.

I comandi funzionano anche in modo coerente tra i sistemi operativi e le versioni di Perl, a differenza del system () incorporato di Perl che potrebbe non verificare la presenza di dati contaminati quando viene chiamato con più argomenti su versioni precedenti di Perl (ad es. 5.6.0 con più argomenti) o che può comunque chiamare la shell in Windows.

Ad esempio, il seguente frammento di codice salverà i risultati di una chiamata in perldoc in uno scalare, evita la shell e genera un'eccezione se la pagina non può essere trovata (poiché perldoc restituisce 1 ).

#!/usr/bin/perl -w
use strict;
use IPC::System::Simple qw(capture);

# Make sure we're called with command-line arguments.
@ARGV or die "Usage: <*> arguments\n";

my $documentation = capture('perldoc', @ARGV);

IPC :: System :: Simple è puro Perl, funziona su 5.6.0 e versioni successive e non ha dipendenze che normalmente non verrebbero con la tua distribuzione Perl. (Su Windows dipende da un modulo Win32 :: fornito con ActiveState e Strawberry Perl).

Disclaimer: sono l'autore di IPC :: System :: Simple , quindi potrei mostrare dei pregiudizi.

Altri suggerimenti

La regola è semplice: non usare mai i backtick se riesci a trovare un built-in per fare lo stesso lavoro, o se il loro è un modulo robusto sul CPAN che lo farà per te. I backtick spesso si basano su codice non portabile e anche se non si sottraggono le variabili, è ancora possibile aprirsi a molte falle di sicurezza.

Mai usa i backtick con i dati degli utenti a meno che tu non abbia specificato molto attentamente ciò che è permesso (non ciò che è vietato - ti mancheranno le cose)! Questo è molto, molto pericoloso.

I backtick dovrebbero essere usati se e solo se devi catturare l'output di un comando. Altrimenti, dovrebbe essere usato system (). E, naturalmente, se c'è una funzione Perl o un modulo CPAN che fa il lavoro, questo dovrebbe essere usato al posto di uno dei due.

In entrambi i casi, due cose sono fortemente incoraggiate:

Innanzitutto, disinfetta tutti gli input: usa la modalità Taint (-T) se il codice è esposto a possibili input non attendibili. Anche se non lo è, assicurati di gestire (o prevenire) personaggi funky come lo spazio o i tre tipi di citazione.

Secondo, controlla il codice di ritorno per assicurarti che il comando abbia avuto successo. Ecco un esempio di come farlo:

my $cmd = "./do_something.sh foo bar";
my $output = `$cmd`;

if ($?) {
   die "Error running [$cmd]";
}

Un altro modo per catturare stdout (oltre al codice pid e di uscita) è usare IPC :: Open3 probabilmente negando l'uso sia del sistema che dei backtick.

Usa i backtick quando vuoi raccogliere l'output dal comando.

Altrimenti system () è una scelta migliore, specialmente se non è necessario invocare una shell per gestire i metacaratteri o l'analisi dei comandi. Puoi evitarlo passando un elenco a system (), ad esempio system ('cp', 'foo', 'bar') (tuttavia probabilmente faresti meglio a usare un modulo per quello < em> particolare esempio :))

In Perl, c'è sempre più di un modo per fare tutto ciò che vuoi. Il punto principale dei backtick è ottenere l'output standard del comando shell in una variabile Perl. (Nel tuo esempio, tutto ciò che viene stampato dal comando cp verrà restituito al chiamante.) L'aspetto negativo di usare i backtick nel tuo esempio è che non controlli il valore di ritorno del comando shell; cp potrebbe fallire e non te ne accorgeresti. Puoi usarlo con la speciale variabile Perl $ ?. Quando voglio eseguire un comando di shell, tendo a usare il sistema :

system("cp $from $to") == 0
    or die "Unable to copy $from to $to!";

(Osserva anche che questo fallirà sui nomi di file con spazi incorporati, ma presumo che non sia questo il punto della domanda.)

Ecco un esempio inventato di dove i backtick potrebbero essere utili:

my $user = `whoami`;
chomp $user;
print "Hello, $user!\n";

Per casi più complicati, puoi anche utilizzare apri come pipe:

open WHO, "who|"
    or die "who failed";
while(<WHO>) {
    # Do something with each line
}
close WHO;

Da " perlop " pagina di manuale:

  

Ciò non significa che dovresti uscire   il tuo modo per evitare contraccolpi quando   sono il modo giusto per ottenere qualcosa   fatto. Perl è stato creato per essere una colla   lingua e una delle cose   le colle insieme sono comandi. Appena   capire cosa stai ricevendo   te stesso in.

Per il caso che stai mostrando usando il File :: Copia Il modulo è probabilmente il migliore. Tuttavia, per rispondere alla tua domanda, ogni volta che devo eseguire un comando di sistema mi affido in genere a IPC: : Run3 . Fornisce molte funzionalità come la raccolta del codice di ritorno e l'output standard ed errore.

Qualunque cosa tu faccia, oltre a disinfettare l'input e verificare il valore di ritorno del tuo codice, assicurati di chiamare tutti i programmi esterni con il loro percorso esplicito e completo. per esempio. dire

my $user = `/bin/whoami`;

o

my $result = `/bin/cp $from $to`;

Dire solo " whoami " o " cp " corre il rischio di eseguire accidentalmente un comando diverso da quello previsto, se il percorso dell'utente cambia, ovvero una vulnerabilità di sicurezza che un utente malintenzionato potrebbe tentare di sfruttare.

Il tuo esempio è negativo perché ci sono built-in perl per fare ciò che sono portatili e di solito più efficienti dell'alternativa backtick.

Dovrebbero essere usati solo quando non c'è alternativa al modulo (o al modulo) Perl. Questo è sia per i backtick che per le chiamate di sistema (). I backtick sono intesi per catturare l'output del comando eseguito.

I backtick dovrebbero essere usati solo quando vuoi catturare l'output. Usarli qui " sembra sciocco. & Quot; Indizierà chiunque guardi il tuo codice sul fatto che non hai molta familiarità con Perl.

Usa i backtick se vuoi catturare l'output. Utilizzare il sistema se si desidera eseguire un comando. Un vantaggio che otterrai è la possibilità di controllare lo stato del reso. Utilizzare i moduli ove possibile per la portabilità. In questo caso, File :: Copia si adatta al conto.

In generale, è meglio usare sistema invece di backtick perché:

  1. sistema incoraggia il chiamante a controllare il codice di ritorno del comando.

  2. sistema consente " oggetto indiretto " notazione, che è più sicura e aggiunge flessibilità.

  3. I backtick sono culturalmente legati allo scripting della shell, che potrebbe non essere comune tra i lettori del codice.

  4. I backtick usano una sintassi minima per quello che può essere un comando pesante.

Uno dei motivi per cui gli utenti potrebbero essere tentati di utilizzare i backtick anziché il sistema è nascondere STDOUT all'utente. Ciò si ottiene più facilmente e in modo flessibile reindirizzando il flusso STDOUT:

my $cmd = 'command > /dev/null';
system($cmd) == 0 or die "system $cmd failed: $?"

Inoltre, sbarazzarsi di STDERR è facilmente realizzabile:

my $cmd = 'command 2> error_file.txt > /dev/null';

Nelle situazioni in cui ha senso usare i backtick, preferisco usare il qx {} per sottolineare che si sta verificando un comando pesante.

D'altra parte, avere un altro modo di farlo può davvero aiutare. A volte devi solo vedere cosa stampa un comando su STDOUT. I backtick, se usati come negli script di shell, sono solo lo strumento giusto per il lavoro.

Perl ha una doppia personalità. Da un lato è un ottimo linguaggio di scripting che può sostituire l'uso di una shell. In questo tipo di utilizzo unico di I-watching-the-outcome, i backtick sono convenienti.

Se utilizzato un linguaggio di programmazione, è necessario evitare i backtick. Questa è una mancanza di errore controllo e, se i backtick di programma separati possono essere evitati, l'efficienza è guadagnato.

A parte quanto sopra, la funzione di sistema dovrebbe essere usata quando non viene utilizzato l'output del comando.

I backtick sono per i dilettanti. La soluzione a prova di proiettile è un "tubo di sicurezza aperto" (vedi " man perlipc "). Esegui il tuo comando in un altro processo, che ti consente di eseguire prima futz con STDERR, setuid, ecc. Vantaggi: non si affida alla shell per analizzare @ARGV, a differenza di open (" $ cmd $ args | "), che non è affidabile. È possibile reindirizzare STDERR e modificare i privilegi dell'utente senza modificare il comportamento del programma principale. Questo è più dettagliato dei backtick ma puoi avvolgerlo nella tua stessa funzione come run_cmd ($ cmd, @ args);


sub run_cmd {
  my $cmd = shift @_;
  my @args = @_;

  my $fh; # file handle
  my $pid = open($fh, '-|');
  defined($pid) or die "Could not fork";
  if ($pid == 0) {
    open STDERR, '>/dev/null';
    # setuid() if necessary
    exec ($cmd, @args) or exit 1;
  }
  wait; # may want to time out here?
  if ($? >> 8) { die "Error running $cmd: [$?]"; }
  while (<$fh>) {
    # Have fun with the output of $cmd
  }
  close $fh;
}
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top