runprogram Command in Mac OS

Mac OSX specific forum
JohnF
New User
New User
Posts: 6
Joined: Thu Dec 14, 2023 8:47 am

runprogram Command in Mac OS

Post by JohnF »

I've been successful using runprogram to call Exiftool (a widely distributed command line program that accesses image files and their associated meta data) in Windows

I have two cases where I call exiftool. Below are the code snippets for the Windows implementation, which work as expected.

The first case (creating a comprehensive list of keywords) includes sorting, reducing the list to unique entries, and outputting to a file. Because this is piped it required that I execute it by first calling "cmd /c". Here's the working Windows code.

Code: Select all

CodeSelect
  Workingdirectory$=""
  Commandline$ = " /c exiftool -r " + Chr(34) + Input_Directory$ + Chr(34) + " -Keywords -b -sep " + Chr(34) + "\n" + Chr(34) + " -sep " + Chr(34) + "\n" + Chr(34) + " | sort /unique > " + Chr(34) + Output_Directory$ + "Keyword Listing.txt" + Chr(34)
  Result = RunProgram("cmd",Commandline$,Workingdirectory$,#PB_Program_Hide|#PB_Program_Wait)
The second case (selectively copying images to an output folder) was achieved by invoking exiftool directly. Here's the working Windows code:

Code: Select all

  Exiftool$ = "exiftool"
  Workingdirectory$=""
  Commandline$ = " -r " + Chr(34) + Input_Directory$ + Chr(34) + " -if " + Chr(34) + "$rating >="+Rating$ + Keyword_Filter$ + Chr(34) + " -o " + Chr(34) + Output_Directory$ + Output_String$ + Chr(34)
  Result=RunProgram(Exiftool$,Commandline$,Workingdirectory$,#PB_Program_Hide | #PB_Program_Wait)
Both work just as they should in Windows.

So I'm 100% new to the mac world. I've scoured the internet, looking also for python equivalent examples, etc. and seen some posts on this site too. I've believe I've accounted for special characters and have substituted with Char(39) to use a single quote where needed (though not reflected in the original working Windows posted above). It seems that my difficulties are more fundamental and that exiftool is never really even called. I've experimented with using either "open", "sh" or "bash" in the same fashion as "cmd /c" but as of yet, no luck. As mentioned in my previous post, when directly entering a good command string in to a Terminal window, things are good.

On the Mac, how do I call exiftool with all the associated syntax for these two cases - one to mimic using "cmd /c" as the principle program called with exiftool and its syntax passed as the argument and the other to call exiftool and its argument directly? As you can see my calling program (PureBasic) is flexible - it allows me to specify the command, command line string, and working directory. In addition, I can stipulate if I wish to have it execute hidden, to hold execution, etc..

An answer to either would be great, both would be fantastic. Do you have a tutorial on this?
User avatar
mk-soft
Always Here
Always Here
Posts: 6204
Joined: Fri May 12, 2006 6:51 pm
Location: Germany

Re: runprogram Command in Mac OS

Post by mk-soft »

Not testet ...

Code: Select all

Exiftool$ = "exiftool"
  Workingdirectory$=""
  Commandline$ = " -r " + Chr(34) + Input_Directory$ + Chr(34) + " -if " + Chr(34) + "$rating >="+Rating$ + Keyword_Filter$ + Chr(34) + " -o " + Chr(34) + Output_Directory$ + Output_String$ + Chr(34)
  Result=RunProgram("open", "-a exiftool" + Commandline$,Workingdirectory$,#PB_Program_Hide | #PB_Program_Wait)
  
  Debug Result
My Projects ThreadToGUI / OOP-BaseClass / EventDesigner V3
PB v3.30 / v5.75 - OS Mac Mini OSX 10.xx - VM Window Pro / Linux Ubuntu
Downloads on my Webspace / OneDrive
User avatar
Piero
Addict
Addict
Posts: 865
Joined: Sat Apr 29, 2023 6:04 pm
Location: Italy

Re: runprogram Command in Mac OS

Post by Piero »

Code: Select all

Procedure simpleShell(ShellCommand$)
   Protected shell
   shell = RunProgram("/bin/zsh","","", #PB_Program_Open|#PB_Program_Write)
   If shell
      WriteProgramStringN(shell,ShellCommand$)
      WriteProgramData(shell,#PB_Program_Eof,0)
      While ProgramRunning(shell) : Delay(10) : Wend ; Wait until program quits (optional)
      CloseProgram(shell)
   EndIf
EndProcedure

Structure daResults
   Out.s
   Err.s
   ExC.w ; exit code
EndStructure

Global Sh.daResults

Procedure Shell(ShellCommand$, AddReturns = #True)
   Protected Err$, tmper$, Output$, shell, Exc.w = -1 ; -1 on failed launch
   shell = RunProgram("/bin/zsh","","",
      #PB_Program_Open|#PB_Program_Write|#PB_Program_Read|#PB_Program_Error )
   If shell
      WriteProgramStringN(shell,ShellCommand$)
      WriteProgramData(shell,#PB_Program_Eof,0)
      While ProgramRunning(shell)
         If AvailableProgramOutput(shell)
            Output$ + ReadProgramString(shell)
            If AddReturns : Output$ + Chr(13) : EndIf
         EndIf
         tmper$ = ReadProgramError(shell)
         If tmper$ : Err$ + tmper$ + Chr(13) : EndIf
      Wend
      Exc = ProgramExitCode(shell) : CloseProgram(shell)
   EndIf
   Sh\Out = Output$ : Sh\Err = Err$ : Sh\ExC = Exc
EndProcedure


; Print Single-Quoted Hello in Terminal: echo \'Hello\' 

rprg = RunProgram("echo", ~"\\'Hello\\'", "", #PB_Program_Open | #PB_Program_Read)
If rprg
   While ProgramRunning(rprg)
      If AvailableProgramOutput(rprg)
         Output$ + ReadProgramString(rprg)
      EndIf
   Wend
   CloseProgram(rprg)
EndIf
Debug Output$ ; Ooops!!!!!!!

Debug ~"\r------\r\r"

Shell(~"echo \\'Hello\\'") ; uses WriteProgramString to zsh
Debug Sh\Out

Shell(~"echo '\"Hello\"'") ; double-quoted (in Terminal: echo '"Hello"')
Debug Sh\Out
Edit: added simpleShell
Last edited by Piero on Fri May 03, 2024 10:37 am, edited 2 times in total.
User avatar
Piero
Addict
Addict
Posts: 865
Joined: Sat Apr 29, 2023 6:04 pm
Location: Italy

Re: runprogram Command in Mac OS

Post by Piero »

Also,

Code: Select all

Procedure.s QuoteString(str$) ; useful to quote FILE/FOLDER PATHS
   ProcedureReturn "'" + ReplaceString(str$, "'", "'\''") + "'"
EndProcedure

; if you want it to behave EXACTLY like Terminal,
; e.g., recognize commands added to $PATH:
Macro Terminal(SHELLCOMMAND)
   Shell("zsh -lc " + QuoteString(SHELLCOMMAND)) ; (Shell from post above)
EndMacro ; note for Gurus: this doesn't work for stuff (functions, aliases…) in ~/.zshrc

; use URLEncoder() for URLs
Edit: changed QuotePath to QuoteString and added some tips
Last edited by Piero on Fri May 03, 2024 10:44 pm, edited 3 times in total.
User avatar
Piero
Addict
Addict
Posts: 865
Joined: Sat Apr 29, 2023 6:04 pm
Location: Italy

Re: runprogram Command in Mac OS

Post by Piero »

Dedicated to mk (if this code can be useful)

Code: Select all

; Mostly sh this time (Terminal uses zsh…)

Structure daResults : Out.s : Err.s : ExC.w : EndStructure
Global Sh.daResults, daShell$="/bin/sh"

Procedure z_shShell(ShellCommand$, AddReturns = #True)
   Protected Err$, tmper$, Output$, shell, Exc.w =-1 ; exit code -1 on failed launch
   shell = RunProgram(daShell$,"","",
      #PB_Program_Open|#PB_Program_Write|#PB_Program_Read|#PB_Program_Error )
   If shell
      WriteProgramStringN(shell,ShellCommand$)
      WriteProgramData(shell,#PB_Program_Eof,0)
      While ProgramRunning(shell)
         If AvailableProgramOutput(shell)
            Output$ + ReadProgramString(shell)
            If AddReturns : Output$ + Chr(13) : EndIf
         EndIf
         tmper$ = ReadProgramError(shell)
         If tmper$ : Err$ + tmper$ + Chr(13) : EndIf
      Wend
      Exc = ProgramExitCode(shell) : CloseProgram(shell)
   EndIf
   Sh\Out = Output$ : Sh\Err = Err$ : Sh\ExC = Exc
EndProcedure

Macro zsh(s,r=#True) : daShell$="/bin/zsh" : z_shShell(s,r) :EndMacro
Macro sh(s,r=#True)  : daShell$="/bin/sh" : z_shShell(s,r) : EndMacro
Macro as(s,r=#False) : sh(~"osascript\n"+s,r) : EndMacro ; sh applescript (osascript)
Macro js(JS,r=#False) ; sh JAVASCRIPT (osascript)
   sh( ~"osascript -l JavaScript\n(function(){" + JS + "})()" ,r)
EndMacro

; **************************************************************

zsh(~"a=1;b=1;if [ $a -eq $b ];then;echo \"Same\";else;echo \"Different\";fi")
Debug sh\Out ; zsh script

sh(~"a=1\nb=2\nif [ $a -eq $b ]\nthen\necho \"Same\"\nelse\necho \"Different\"\nfi")
Debug sh\Out ; sh script

sh(~"cd /NO/DIR/ERROR || echo 'WHOOOPS! Shell Error!  -->  '", #False) ; , #False: don't add returns
Debug sh\Out + sh\Err ; sh (error) || echo

js( ~"var a=1;var b=1;if(a!=b){return'Different';}else{return'Same';}" )
Debug sh\Out ; sh javascript (osascript)

as(~"tell app\"finder\"\nactivate\nbeep\nend\n\"Finder Just Beeped\"")
Debug sh\Out ; sh AppleScript (osascript) - "return" not needed in this case

as(~"tell app \"Finder\" to display dialog \"Copy AppleScript to Clipboard?\"with icon stop"):If sh\Err:End:EndIf
; Puts an AppleScript "like as if copied in Script Editor" on clipboard
as(~"set the clipboard to \"tell application \\\"Finder\\\" -- tell finder\\ractivate\\rbeep\\rend tell\\r\\\"Finder Just Beeped\\\"\"")

; Translates AppleScript code on clipboard to obtain a PB string like below
; Better compile your AppleScript code, before copying it…
Define MyAScr.s = ~"osascript\n"+
~"set c to the clipboard as string\n"+
~"property dacut : \"\\\\n\\\"+\\r~\\\"\"\n"+
~"set pbt to \"\"\n"+
~"set rc to false -- false = do not remove comments\n"+
~"try\n"+
~"display dialog \"Remove -- Comments?\\r\\rAVOID -- in strings!!!\" buttons {\"Keep\", \"Remove\"} cancel button 1 default button 2\n"+
~"set rc to true\n"+
~"end try\n"+
~"repeat with i in paragraphs of c\n"+
~"set i to i as string\n"+
~"set i to replace_chars(i, tab, \"\") -- editor MUST use TABS to indent...\n"+
~"if rc then -- remove comments\n"+
~"set o to offset of \"--\" in i\n"+
~"if o > 1 then\n"+
~"set i to (characters 1 thru (o - 2) of i) as string\n"+
~"else if o = 1 then\n"+
~"set i to \"\"\n"+
~"end if\n"+
~"end if\n"+
~"if i is not \"\" then\n"+
; \ in AppleScript strings: "\\"
~"set i to replace_chars(i, \"\\\\\", \"\\\\\\\\\") -- backslash to double backslash\n"+
~"set i to replace_chars(i, \"\\\"\", \"\\\\\\\"\") -- double-quote to backslash double-quote\n"+
~"set pbt to pbt & \"\\\\n\" & i\n"+
~"end if\n"+
~"end repeat\n"+
~"set pbt to \"Define MyAScr.s = ~\\\"osascript\" & pbt & \"\\\"\\r\\r\"\n"+
~"if (count pbt) > 80 then\n"+
~"try\n"+
~"display dialog \"CUT?\" buttons {\"NO\", \"Cut\"} cancel button 1 default button 2\n"+
~"copy replace_chars(pbt, \"\\\\n\", dacut) to pbt\n"+
~"end try\n"+
~"end if\n"+
~"set dla to display dialog \"Set the Clipboard to this?\\r\\rYou can edit here…\" default answer pbt buttons {\"Cancel\", \"Copy\"} cancel button 1 default button 2\n"+
~"set the clipboard to text returned of dla -- set the clipboard to pbt -- no editing\n"+
~"on replace_chars(this_text, search_string, replacement_string)\n"+
~"local asd\n"+
~"set asd to AppleScript's text item delimiters\n"+
~"set AppleScript's text item delimiters to the search_string\n"+
~"set the item_list to every text item of this_text\n"+
~"set AppleScript's text item delimiters to the replacement_string\n"+
~"set this_text to the item_list as string\n"+
~"set AppleScript's text item delimiters to asd\n"+
~"return this_text\n"+
~"end replace_chars"

sh(MyAScr)
Last edited by Piero on Sun Feb 11, 2024 12:43 am, edited 4 times in total.
User avatar
Piero
Addict
Addict
Posts: 865
Joined: Sat Apr 29, 2023 6:04 pm
Location: Italy

Re: runprogram Command in Mac OS

Post by Piero »

Now let's see some Ruby examples:

Code: Select all

; I'm kidding, but in case explore TextMate
 
Edit: Dear wise forum admins, why do I often get duplicates when editing a post? (even when changing only one letter... Safari)
Edit2: There are 2 possibilities:
1) You fixed it (I thought it could be "preview")
2) (more probable) This noob was clicking "quote" instead of "edit" :oops:
Edit3: BTW, if it wasn't a Miracle
Edit4: Added the Miracle possibility
Last edited by Piero on Fri May 03, 2024 6:46 pm, edited 3 times in total.
JohnF
New User
New User
Posts: 6
Joined: Thu Dec 14, 2023 8:47 am

Re: runprogram Command in Mac OS

Post by JohnF »

Thank you for the extended reply which, if I understand correctly allows for extended two-way communication between PureBasic and the executable that is being called.

My application is quite simple, and luckily I've made progress, where I am able to launch and send a commandline$ argument to the executable. The problem I am now running into is that if the arguments have spaces in them then using either single or double quotes are not recognized. Here's an example. It's all a bit confusing because I'm not having this difficulty in the Windows version.

Commandline$ = " -LensMake=Leica Filename.DNG"
Result = RunProgram("/usr/local/bin/exiftool",Commandline$,"")
Works fine.

When I set Commandline$ = " -LensMake=Leica AG" then all that is written is Leica and the " AG" is lost.

When I try Commandline$ = " -LensMake='Leica AG'" then what gets written is 'Leica meaning the single quote is just treated as text and has no significance.

I get the same result if I programmatically try to construct the Commandline$ like this - using double quotes - Chr(34) or single quotes Char(39)
Commandline$ = " -LensMake=" + Chr(39) + "Leica AG" + Chr(39)

On the other hand, if I type the same command directly into a terminal window
/usr/local/bin/exiftool -LensMake='Leica AG' Filename.DNG works just as it should and correctly passes the full Leica AG.

Would you be able to suggest what syntax to I need to add so that I can pass string information with blank spaces in MacOS?

Thanks a million if you can help with this.
Last edited by JohnF on Wed Apr 10, 2024 5:54 am, edited 1 time in total.
User avatar
Piero
Addict
Addict
Posts: 865
Joined: Sat Apr 29, 2023 6:04 pm
Location: Italy

Re: runprogram Command in Mac OS

Post by Piero »

JohnF wrote: Wed Apr 10, 2024 12:16 amif you can help with this.
Huh? If you use the shell procedure I posted above, you must get the same results you get in Terminal!
In case, you only have to properly escape \ and " in the PB ~"string"...
JohnF
New User
New User
Posts: 6
Joined: Thu Dec 14, 2023 8:47 am

Re: runprogram Command in Mac OS

Post by JohnF »

Can you help me with-> you only have to properly escape \ and " in the PB ~"string"...

I'm afraid there's much I don't know, so giving me and example would be helpful.
User avatar
Piero
Addict
Addict
Posts: 865
Joined: Sat Apr 29, 2023 6:04 pm
Location: Italy

Re: runprogram Command in Mac OS

Post by Piero »

Code: Select all

Debug ~" \\ \" "
JohnF
New User
New User
Posts: 6
Joined: Thu Dec 14, 2023 8:47 am

Re: runprogram Command in Mac OS

Post by JohnF »

Hi Piero,

Thanks for your reply, but I'm afraid I don't quite know what to make of it.

Can you apply your fix to:

Commandline$ = " -LensMake=Leica AG" ?

Many thanks!
John
User avatar
Piero
Addict
Addict
Posts: 865
Joined: Sat Apr 29, 2023 6:04 pm
Location: Italy

Re: runprogram Command in Mac OS

Post by Piero »

From my 1st post above:

Code: Select all

Shell("/usr/local/bin/exiftool -LensMake=Leica AG")
if it works on Terminal, otherwise try:

Code: Select all

Shell("/usr/local/bin/exiftool -LensMake='Leica AG'")
or

Code: Select all

Shell(~"/usr/local/bin/exiftool -LensMake=\"Leica AG\"")
User avatar
Piero
Addict
Addict
Posts: 865
Joined: Sat Apr 29, 2023 6:04 pm
Location: Italy

Re: runprogram Command in Mac OS

Post by Piero »

JohnF wrote: Wed Apr 10, 2024 7:25 amMany thanks!
John
BTW, you are very, VERY welcome: I entered this forum for the same reason, screaming with indignation that runprogram wasn't working
:lol: :mrgreen:
JohnF
New User
New User
Posts: 6
Joined: Thu Dec 14, 2023 8:47 am

Re: runprogram Command in Mac OS

Post by JohnF »

Hello Piero,

I'm very pleased to know that others have gone completely mad trying to get RunProgram to function as desired. I've been completely successful when compiling and operating under WINDOWS. My trouble seems to be wholly centered around how strings are treated and passed in the MacOS environment.

I tried your suggestions below. Without success. One caveat, I am not using the full implementation that have provide, - just trying to executed the desired function directly in a single call. As mentioned, this had been completely successful for me in WINDOWS.

Here's what works in in MacOS when typing directly into a Terminal window. - I'm now giving the full command string

/usr/local/bin/exiftool -LensMake=Voigtlander -LensModel='Color Skopar' -preserve -overwrite_original_in_place -X -m /Users/Elizabeth/Desktop/John/Temp

In the Terminal window, the single (or double quotes) are recognized just as they should.

The problem is I can find no way to pass the values for parameters (ie -LensMake= or -LensModel=) that contain spaces. When I execute the following

Result = RunProgram("/usr/local/bin/exiftool",~" -LensMake=Voigtlander -LensModel=\"Color Skopar\" -preserve -overwrite_original_in_place -X -m /Users/Elizabeth/Desktop/John/Temp","",#PB_Program_Hide|#PB_Program_Wait)

the assignment to LensModel is "Color. It takes the double quote (or single quote if I try that) as part of the value. I have tried building d Commandline$ string and passing it. I get the same results. With the Commandline$ approach I've also tried using #DQUOTE$ and Chr(34) and Chr(39) around the arguments as well. In every case the quote that I'm inserting (using any mechanism) is simply treated as the first character of the value I'm assigning.

I've yet to find any syntax that allows me to pass the a value for -LensModel where the space is not parsed in the overall command line.

As mentioned, this is all working under WINDOWS. Why is this so difficult on MacOS?

Thanks for your willingness and expertise.

Best,
John
User avatar
Piero
Addict
Addict
Posts: 865
Joined: Sat Apr 29, 2023 6:04 pm
Location: Italy

Re: runprogram Command in Mac OS

Post by Piero »

It seems you don't need to get the output, so you can use a simplified shell procedure if you want:

Code: Select all

Procedure simpleShell(ShellCommand$)
   Protected shell
   shell = RunProgram("/bin/zsh","","", #PB_Program_Open|#PB_Program_Write)
   If shell
      WriteProgramStringN(shell,ShellCommand$)
      WriteProgramData(shell,#PB_Program_Eof,0)
      While ProgramRunning(shell) : Delay(10) : Wend ; Wait until program quits
      CloseProgram(shell)
   EndIf
EndProcedure

simpleShell("/usr/local/bin/exiftool -LensMake=Voigtlander -LensModel='Color Skopar' -preserve -overwrite_original_in_place -X -m /Users/Elizabeth/Desktop/John/Temp")
Edit/PS: if your mac os is very old, use /bin/sh instead of /bin/zsh
JohnF wrote: Wed Apr 10, 2024 5:34 pm|#PB_Program_Wait)
Edit 2: Added "Wait until program quits"
Post Reply