Forum OpenACS Development: exec bash command pdftk within TCL page

Hi there,

I wrote a little application to merge n pdf files into 1 that executes the bash command bellow:

pdftk $files cat output $output_file

However I get an error of no such file while executing it.
The TCL script is an ad_form that gets all the n files from user input, within the on_submit block it merges the files and returns to the user a single pdf file.

-on_submit {
set files ""
for { set i 0 } { $i < $count } { incr i} {

set tmpfile [set file_${i}]

set filename [ns_tmpnam]
file copy [lindex $tmpfile 1] $filename

append files "$filename "
ns_log Notice "ONSUBMIT $files"
}

set output_file "[ns_tmpnam].pdf"

ns_log Notice "INPUT: $files"
ns_log Notice "OUTPUT: $output_file"

set command "pdftk $files cat output $output_file"
ns_log Notice "$command"

exec $command

#if {![catch { exec $command } errorMsg] {
# do nothing
#}

ns_returnfile 200 application/pdf $output_file
}

It would be a little and easy script if it wasn't for the error I get.
I tested the whole script mannualy, also wrote ns_logs to verify the commands. It seems TCL doesn't parse properly the value within $files.

If I determine static files as in "pdftk pdf_0.pdf pdf_1.pdf cat output $output_file"
It works fine. Moreover I printed the content assgined to $files and there's nothing wrong with it.

Have anyone once faced the same issue?

[16/Aug/2011:10:26:50][976.3052518256][-default:0-] Notice: ONSUBMIT
[16/Aug/2011:10:26:50][976.3052518256][-default:0-] Notice: INPUT: /tmp/pdf_0.pdf /tmp/pdf_1.pdf
[16/Aug/2011:10:26:50][976.3052518256][-default:0-] Notice: OUTPUT: /tmp/fileB5eWgF.pdf
[16/Aug/2011:10:26:50][976.3052518256][-default:0-] Notice: pdftk /tmp/pdf_0.pdf /tmp/pdf_1.pdf cat output /tmp/fileB5eWgF.pdf
[16/Aug/2011:10:26:50][976.3052518256][-default:0-] Warning: blank-compat: /var/www/mda/www/pdfmerge.adp uses deprecated property title instead of doc(title).
[16/Aug/2011:10:26:50][976.3052518256][-default:0-] Warning: blank-compat: /var/www/mda/www/pdfmerge.adp uses deprecated property header_stuff instead of head.
[16/Aug/2011:10:26:50][976.3052518256][-default:0-] Error: POST http://10.0.105.227:8000/pdfmerge?
referred by "http://10.0.105.227:8000/pdfmerge";
couldn't execute "pdftk /tmp/pdf_0.pdf /tmp/pdf_1.pdf cat output /tmp/fileB5eWgF.pdf": no such file or directory
while executing
"exec $command"
("uplevel" body line 24)
invoked from within
"uplevel #$level $on_submit"
("1" arm line 1)
invoked from within
"switch $errno {
0 {
# TCL_OK
}
1 {
# TCL_E..."

Collapse
Posted by Dave Bauer on
It is because you are passing the command as a string.

So the entire command line you are passing is interpreted as the command name.

Each argument to the command line has to be a seperate argument to exec.

Try

eval {exec $files}

Collapse
Posted by Victor Guerra on
Or in case you are using tcl >= 8.5 you can use the argument expansion feature:

...
exec {*}$command
....

Best,

Collapse
Posted by Iuri Sampaio on
The problem must be something else.

The code is very simple and straight forward

ad_form -extend -name files -on_submit {
ns_log Notice "ONSUBMIT "

set files [list]
for { set i 0 } { $i < $count } { incr i} {

set tmpfile [set file_${i}]

set filename "/tmp/pdf_$i.pdf"
file copy [lindex $tmpfile 1] $filename

lappend files $filename
}

set output_file "[ns_tmpnam].pdf"

ns_log Notice "INPUT: $files"
ns_log Notice "OUTPUT: $output_file"

eval { exec pdftk ${files} cat output $output_file}

ns_returnfile 200 application/pdf $output_file
}

and the error is still

[16/Aug/2011:13:04:06][976.3003374448][-default:10-] Notice: ONSUBMIT
[16/Aug/2011:13:04:06][976.3003374448][-default:10-] Notice: INPUT: /tmp/pdf_0.pdf /tmp/pdf_1.pdf
[16/Aug/2011:13:04:06][976.3003374448][-default:10-] Notice: OUTPUT: /tmp/filexx9qHV.pdf
[16/Aug/2011:13:04:07][976.3003374448][-default:10-] Warning: blank-compat: /var/www/mda/www/pdfmerge.adp uses deprecated property title instead of doc(title).
[16/Aug/2011:13:04:07][976.3003374448][-default:10-] Warning: blank-compat: /var/www/mda/www/pdfmerge.adp uses deprecated property header_stuff instead of head.
[16/Aug/2011:13:04:07][976.3003374448][-default:10-] Error: POST http://10.0.105.227:8000/pdfmerge?
referred by "http://10.0.105.227:8000/pdfmerge";
/tmp/pdf_0.pdf /tmp/pdf_1.pdf not found as file or resource.
Error: Failed to open PDF file:
/tmp/pdf_0.pdf /tmp/pdf_1.pdf
Errors encountered. No output created.
Done. Input errors, so no output created.
while executing
"exec pdftk ${files} cat output $output_file"
("eval" body line 1)
invoked from within
"eval { exec pdftk ${files} cat output $output_file}"
("uplevel" body line 20)
invoked from within
"uplevel #$level $on_submit"
("1" arm line 1)
invoked from within
"switch $errno {
0 {
# TCL_OK
}
1 {
# TCL_E..."
(procedure "ad_form" line 629)

Victor, my TCL version is

% info patchlevel
8.4.19
%

Collapse
Posted by Iuri Sampaio on
Btw, I changed the variable, to be placed in the input arguments, from a string to a list. Without expecting too much but just as a last change to see if it works.
Collapse
Posted by Dave Bauer on
It says the tmp pdf files don't exist

/tmp/pdf_0.pdf /tmp/pdf_1.pdf not found as file or resource.
Error: Failed to open PDF file:

That is unrelated to exec.

Either its a permissions issue or the files don't exist.

Collapse
Posted by Iuri Sampaio on
Impossible.

I see the files within /tmp
Plus I set the directory to be 777 by default. Recursively!

Collapse
Posted by Iuri Sampaio on
mda@ubuntu:~$ ls -l /tmp
total 744
-rwxrwxrwx 1 mda web 668409 2011-08-16 13:22 pdf_0.pdf
-rwxrwxrwx 1 mda web 89202 2011-08-16 13:22 pdf_1.pdf
Collapse
Posted by Iuri Sampaio on
my guess is TCL parsing treats "/tmp/pdf_0.pdf /tmp/pdf_1.pdf" as an unique file instead of two
Collapse
Posted by Iuri Sampaio on
Okay, I was correct.

If the list contains just one file the script works fine.
The issue now is:

1) why does it happen?
2) How to pass two or more files to the command line using a TCL variable?

Cheers,

Collapse
Posted by Dave Bauer on
You file list is being taken as 1 argument

/tmp/pdf_0.pdf /tmp/pdf_1.pdf not found as file or resource

means

"/tmp/pdf_0.pdf /tmp/pdf_1.pdf" not found as file or resource

Collapse
Posted by Dave Bauer on
Aha you need

eval [list exec pdftk ${files} cat output $output_file]

not with { }

Collapse
Posted by Iuri Sampaio on
lol, i am laughing loud with the 6 rules

http://mark.aufflick.com/blog/2005/11/10/six-stages-of-debugging

Nice read!!

The command
eval [list exec pdftk ${files} cat output $output_file]

didn't work either.
As soon as i figure that out I post here.
Thanks for the feedback!!!

Collapse
Posted by Iuri Sampaio on
Good news! ns_eval solved the problem.

set command "pdftk $files cat output $output_file"

ns_eval exec $command

Collapse
Posted by Iuri Sampaio on
Why is another issue that I will understand now.

Thanks Dave, Thanks Victor

Collapse
Posted by Jeff Rogers on
You should use plain eval, not ns_eval.

ns_eval is used for reinitializing all the tcl interpreters in the server, something you generally don't want to be doing a lot of on a production server.

Collapse
Posted by Brian Fenton on
Iuri

I did almost exactly the same thing a while back using ImageMagick. Here's my code if it's any use to you.

ad_proc -public pdf::concat_files {
    {-input_file_list:required}
    {-output_file:required}
}  {

   Makes an operating system call to ImageMagick to concatenate a list of PDF documents.
   ImageMagick is a good choice here as it runs on both Windows and Unix.
   For a Unix-only implementation, I would have used PDFJam/PDFjoin or PDFsam.
    

    @author Brian Fenton Oct 2009

    @param input_file_list - a list of input files (full path names)
    @param output_file -  full path name of the output file 

    @return 1 if all ok, 0 if we have a problem

} {

  #Initialise 
  set return_val 1  

  #if only 1 input file passed in then skip the concatenation
  if { [llength $input_file_list] > 1 } {

    #get location of ImageMagick executable
    set convert_path [parameter::get_from_package_key -package_key acs-workflow -parameter imagemagick_convert_path]

    if {$convert_path ne ""} {
      #set exec_call " $convert_path "
      set exec_call [list $convert_path ]
      foreach infile $input_file_list {
        #check input file is good - exists, permissions etc
        if {![file exists $infile]} {
          set return_val 0
          ns_log Error "pdf::concat_files had an error reading file: $infile not found"
          break
        }

        if {![file readable $infile]} {
          set return_val 0
          ns_log Error "pdf::concat_files had an error reading file: $infile permission denied"
          break
        }

        lappend exec_call "$infile"
      } ;# end foreach

      if {$return_val} {
        #make the call and catch any errors     
        #the ImageMagick generated PDFs are huge, so let's try some compression
        set caught [catch {eval exec -keepnewline $exec_call "-compress Zip" {$output_file}} result]
        if { $caught } {
          set return_val 0
          ns_log Error "pdf::concat_files had an error calling $exec_call. The error was $result"
        } else {
          #check did the concatenated file get created
          if {![file exists $output_file]} {
            set return_val 0
            ns_log Error "pdf::concat_files failed to create $output_file"
          }
        }
      }
    } else {
      set return_val 0
      ns_log Error "pdf::concat_files had an error: ImageMagick not found at $convert_path."
    }
  } else {
    #only 1 input file was passed in, so just do a file copy 
    file copy -force [lindex $input_file_list 0] $output_file
  }
                         
  return $return_val
  
}