Cuando pones un '\' entre comillas dobles, se produce una "interpolación" de las variables y de los caracteres "escapados" por el carácter '\'.
Entonces,
Using perl Syntax Highlighting
my $variable1 = "nombre";
my $variable2 = "archivo";
my $nombre = "$variable1\ $variable2.csv";
Coloreado en 0.001 segundos, usando
GeSHi 1.0.8.4
hace que $nombre sea 'nombre archivo'. Y cuando se lo pasamos a system(), como estamos usando otras comillas dobles, se produce otra interpolación, por lo que
system ("touch $nombre");se transforma en
system ('touch nombre archivo');Y esa es la razón de que se creen dos archivos, en lugar de uno solo.
Solución clásicaComo nuestra cadena de caracteres va a sufrir dos interpolaciones, necesitamos "retrasar" el efecto del "escapado" de la barra diagonal inversa hasta el momento de la segunda interpolación:
Using perl Syntax Highlighting
my $variable1 = "nombre";
my $variable2 = "archivo";
my $nombre = "$variable1\\ $variable2.csv";
system("touch $nombre"); # touch nombre\ archivo.csv
Coloreado en 0.001 segundos, usando
GeSHi 1.0.8.4
La barra diagonal pasa al shell, y éste se encargará de escapar el espacio en blanco, por lo que se creará un solo archivo, con el espacio en medio.
Solución Perl 1Si sospechamos que tenemos "caracteres peligrosos" a la hora de crear archivos, podemos pedirle a Perl que los "escape" por nosotros:
Using perl Syntax Highlighting
my $variable1 = "nombre";
my $variable2 = "archivo";
my $nombre = "\Q$variable1 $variable2.csv\E";
system("touch $nombre"); # touch nombre\ archivo.csv
Coloreado en 0.001 segundos, usando
GeSHi 1.0.8.4
que es lo mismo que usar la función quotemeta():
Using perl Syntax Highlighting
my $variable1 = "nombre";
my $variable2 = "archivo";
my $nombre = quotemeta "$variable1 $variable2.csv";
system("touch $nombre"); # touch nombre\ archivo.csv
Coloreado en 0.001 segundos, usando
GeSHi 1.0.8.4
Las opciones de escapado "\Q...\E" están descritas en
perldoc perlop en la sección
Quote and Quote-like Operators, y de forma más específica en
perldoc -f quotemeta.
Solución Perl 2Podemos también... evitar la segunda interpolación, cambiando la forma de llamar a system():
Using perl Syntax Highlighting
my $variable1 = "nombre";
my $variable2 = "archivo";
my $nombre = "$variable1 $variable2.csv";
system('touch', $nombre); # touch "nombre archivo.csv"
Coloreado en 0.001 segundos, usando
GeSHi 1.0.8.4
De hecho, el problema está en llamar a system() con solo un argumento. Lo que dice el manual en
perldoc -f system es:
system LISTA
system PROGRAMA LISTA
Hace exactamente lo mismo que "exec LISTA", excepto que se hace
primero un fork y el proceso padre espera a que el proceso hijo
termine. Tenga en cuenta que el tratamiento de los argumentos varía en
función del número de argumentos. Si hay más de un argumento en LISTA,
o si la lista es un array con más de un valor, se inicia el programa
indicado en el primer elemento de la lista, con los argumentos
indicados por el resto de la lista. Si sólo hay un argumento escalar,
se comprueba si el argumento tiene metacaracteres, y si hay alguno, el
argumento entero es pasado al shell de comandos para que sea
interpretado (suele ser "/bin/sh -c" en plataformas Unix, pero varía
en otras plataformas). Si no hay metacaracteres shell en el argumento,
es dividido en palabras y pasadas directamente a "execvp", que es más
eficiente.
Así que... si solo pasamos un argumento, se divide en palabras (por los espacios en blanco), y pasadas a la función del sistema para que lo ejecute. De ahí viene el problema de tener que escapar cuando usas las comillas dobles.
La solución, como has visto, es la de llamar a system() con más de un argumento.
¿Cuál elegir? La preferida es la última, ya que deja el código más limpio, y Perl no tiene que hacer ninguna operación de "escapado" previo.
P.D.: En vez de llamar a un comando externo 'touch', es preferible usar un open() de Perl. Así no dependes de la existencia o no de un comando externo ni le pides al sistema que arranque un proceso nuevo.