I want to run some commands using the system() command, I do this way:
execute_command_error("trash-put '/home/$filename'");
Where execute_command_error will report if there was an error with whatever system command it ran. I know I could just unlink the file using Perl commands, but I want to delete stuff using trash-put as it’s a type of recycling program.
My problem is that $filename will sometimes have apostrophes, quotes, and other weird characters in it that mess up the system command or Perl itself.
Generate the command name and arguments as an array, and pass that to system:
This means that Perl does not invoke the shell to do any metacharacter expansion (or I/O redirection, or command piping, or …). It does mean it does exactly what you told it to do.
Borrowing information from the copious collection of comments:
(See also the discussion of ‘exec’ below which is closely related.)
I did intend to use single quotes – I’m demonstrating that the
$filenamedoes not get expanded by Perl or Shell…In my test script, I used ‘my.$file‘, and that gave me a file with a$in the name – as I intended.Adding double quotes around the arguments won’t help with embedded
$, backtick1, ‘$(...)‘ and related notations. You more nearly need single quotes around things, but then you need to rewrite embedded single quotes as “'\''” which generates a single quote to terminate the current single-quoted argument, a backslash-quote combination to represent a single quote, and another single quote to resume the single-quoted argument.Roughly speaking, the way the (Unix) shells treat single quotes is “everything from the first single quote up to the next is literal text, no metacharacters”. So, to get the shell to treat something as literal text, enclose it in single characters. That deals with everything except single quotes themselves. As my comment says, you have to use the 4-character replacement string to get a single quote embedded into the middle of a single quoted argument.
There is probably a neater way to do it than this (using one or two
mapoperations, perhaps), but this should work:You can then join the array to make a single string for transmission to
execute_command.As a general rule, I’ll accept this correction. I’m not sure it is crucial in this instance, though, where the command name is provided by the program and it is only the arguments that are possibly provided the user. The command name ‘trash-put’ is safe from shell expansions (IFS is reset to default by the shell when it starts, so that avenue of attack is not available).
This issue is discussed in the ‘perldoc -f exec’ man page:
1 How do you get a back-tick to display in Markdown?