Reputation: 355
I'm a complete beginner in Perl, so please bear with me if my question is trivial.
I am trying to fix a bug in a rather large Perl project. By some trial and error and print statements I could figure out that the following system call to imagemagick is responsible for the bug:
system(
composite => $tmpfilename,
-gravity => $gravity_combo->get_active_text,
$filename => $tmpfilename2
);
The code exectution just stops at this system call and emits no output. By chance, when trying to get some output from this system call, I discovered that if I substitute the system call by the following
my $command = "composite " . $tmpfilename . " -gravity " . $gravity_combo->get_active_text . " " . $filename . " " . $tmpfilename2;
my $output = `$command`;
the command is executed successfully.
As you can see, the commas and => arrows are substituted by string concatenation and system() is substituted by the backticks, but other than that the commands are identical.
Now I wonder how this happened and if there is a more elegant way to write the command in a working way.
Addendum:
I didn't want to share the complete code because, on one hand, it is the code of a somewhat popular app and I didn't want to give the impression that dilettantes like myself are working on it (I'm not the developer anyway, just a team member dealing mostly with user support on the bug tracker); on the other hand I didn't expect more context to be helpful in this case. But as several users requested more context, and also the problem seems to be more involved than I initially assumed, I will provide the complete code.
The code in question is from a plugin for the screenshot tool Shutter for the Linux desktop. The whole project can be found at https://github.com/shutter-project/shutter, the specific plugin in question is at https://github.com/shutter-project/shutter/blob/master/share/shutter/resources/system/plugins/perl/spwatermark/spwatermark
When launching the plugin a dialog is shown where the user can enter some fields. The dialog looks like this:
When first launching it, it executes the function fct_imagemagick_watermark()
(which starts in line 254). Whenever the refresh or save button is clicked, a signal is sent which relaunches this function (the signals are sent in lines 164 and 167). Inside the function, a subfunction apply_effect()
is called which starts at line 341 and includes two system calls to imagemagick commands in lines 354 and 365. Actually, both system calls fail when the function is called via refresh or save button, but the commands work if they are written using backticks and concatenation dots.
$gravity_combo->get_active_text
is the value of the dropdown menu in the upper right corner of the dialog (for example, "Center"). The two tmpfilename variables are generated around line 227, the filename variable comes from the main program.
That's how far I could understand the code so far, please ask again, if there are further questions!
Addendum 2: Possible values for the variables in the system call from the original question:
$tmpfile = /tmp/euQgBTRpnf.png
$tmpfile2 = /tmp/eftbUp8eNf.png
$filename = /home/user/myimage.png
$gravity_combo = Gtk3::ComboBoxText=HASH(0x55e315db9568)
$gravity_combo->get_active-text = "Center"
Upvotes: 1
Views: 268
Reputation: 66944
There isn't enough information to answer fully but we can explain the difference. It is two-fold.
First, there is that apparent and simple "fat comma" asked about
The
=>
operator (sometimes pronounced "fat comma") is a synonym for the comma except that it causes a word on its left to be interpreted as a string if it begins with a letter or underscore and is composed only of letters, digits and underscores. This includes operands that might otherwise be interpreted as operators, constants, single number v-strings or function calls.
So, the word on its left gets (double) quoted, simply and crudely put -- where some characters or strings which otherwise may be treated as operators or functions now aren't. That shouldn't be of significance since there's only the $filename
which is a variable and that should be clear.
Then the fat-comma based line is a simle list
system(
"composite", $tmpfilename,
"-gravity", $gravity_combo->get_active_text,
$filename, $tmpfilename2
);
(that $filename
gets interpolated anyway so I left out the unneeded quotes)
The second difference is subtler and far more loaded: when you give system
a list, it doesn't invoke the shell but uses a system call directly, whereby the first argument is the command name and others are passed to that command as its arguments.
The other way to run the command, that you found to work, is different: The qx (backticks) forms a string of whatever is passed to it, scans it for shell metacharacters and if any are found passes it to the shell for execution.† There may be subtle variations but mostly that's what it does. That may well be very different.
So an obvious guess is that something in there needs the shell, which it gets in the backticks (qx
) approach but not with a list under system
. One way to test that is to force a shell on this
system('bash', '-c', "composite $tmpfilename ...");
(or, concatenate with spaces: join(' ', 'composite', $tmpfilename, ...)
like you do)
It may even be that the composite
command itself needs to be run out of a shell, due to some environment details or such. (Since from links in comments it appears that the rest of arguments are plain numbers or strings and filenames.) This is just a simple guess.
Really we'd need to see details, but hopefully this helps some.
† While you compose the command as a string anyway, so even if that were then passed to the system
it would be treated the same way.
Upvotes: 5
Reputation: 21
I suppose you could just space out the args like this:
my $cmd =" composite $tempfilename
-gravity $gravity_combo->get_active_text
$filename $tmpfilename2)
";
$cmd =~ tr/\n//d;
$result = `$cmd`;
The => is also called the "fat comma" and is normally used in hash structures, but that is not what you have here.
Ok, I see now. "$gravity_combo->get_active_text" is an executable function. It will not execute if it is within quotes. Does this work?
my $cmd = " composite ". $tempfilename.
" -gravity ". $gravity_combo->get_active_text().
" $filename ". $tmpfilename2;
Ok, I've been watching the comments. I've gone back to the original question and implemented a gravity object so that the calling syntax works. When I call foo() below instead of system(), it looks like it "works" and this string returned should also work in a system call. Consider this code:
use strict;
use warnings;
my $tmpfilename = "temp";
my $filename = "somefile";
my $tmpfilename2 = "temp2";
package Gravity;
use strict;
use warnings;
sub new
{
my $class = shift;
my $me = {};
bless($me, $class);
$me ->{active_text} = "ABC";
return $me;
}
sub get_active_text
{
my $me = shift;
return $me->{active_text};
}
my $gravity_combo = new Gravity;
## call foo() instead of system() and see what it prints...
foo (
composite => $tmpfilename,
-gravity => $gravity_combo->get_active_text,
$filename => $tmpfilename2
);
sub foo
{
print "@_\n"; #system ("@_"); should work?
}
# prints: composite temp -gravity ABC somefile temp2
From what I can tell here, make a helper func like foo() and have it make a string to pass to the system call. It looks like this prevents getting bogged down into quoting rules. The args to foo() above look "pretty" (nicely formatted as per the original question and pleasing at least to my eye).
Upvotes: 0