@@ -11,7 +11,6 @@ import (
1111 "os"
1212 "os/exec"
1313 "path"
14- "path/filepath"
1514 "runtime"
1615 "sort"
1716 "strings"
@@ -121,7 +120,7 @@ func (e *Engine) runCommand(ctx Context, tool types.Tool, input string, toolCate
121120 var extraEnv = []string {
122121 strings .TrimSpace ("GPTSCRIPT_CONTEXT=" + strings .Join (instructions , "\n " )),
123122 }
124- cmd , stop , err := e .newCommand (ctx .Ctx , extraEnv , tool , input )
123+ cmd , stop , err := e .newCommand (ctx .Ctx , extraEnv , tool , input , true )
125124 if err != nil {
126125 if toolCategory == NoCategory {
127126 return fmt .Sprintf ("ERROR: got (%v) while parsing command" , err ), nil
@@ -244,7 +243,11 @@ func appendInputAsEnv(env []string, input string) []string {
244243 return env
245244}
246245
247- func (e * Engine ) newCommand (ctx context.Context , extraEnv []string , tool types.Tool , input string ) (* exec.Cmd , func (), error ) {
246+ func (e * Engine ) newCommand (ctx context.Context , extraEnv []string , tool types.Tool , input string , useShell bool ) (* exec.Cmd , func (), error ) {
247+ if runtime .GOOS == "windows" {
248+ useShell = false
249+ }
250+
248251 envvars := append (e .Env [:], extraEnv ... )
249252 envvars = appendInputAsEnv (envvars , input )
250253 if log .IsDebug () {
@@ -254,9 +257,17 @@ func (e *Engine) newCommand(ctx context.Context, extraEnv []string, tool types.T
254257 interpreter , rest , _ := strings .Cut (tool .Instructions , "\n " )
255258 interpreter = strings .TrimSpace (interpreter )[2 :]
256259
257- args , err := shlex .Split (interpreter )
258- if err != nil {
259- return nil , nil , err
260+ var (
261+ args []string
262+ err error
263+ )
264+ if useShell {
265+ args = strings .Fields (interpreter )
266+ } else {
267+ args , err = shlex .Split (interpreter )
268+ if err != nil {
269+ return nil , nil , err
270+ }
260271 }
261272
262273 envvars , err = e .getRuntimeEnv (ctx , tool , args , envvars )
@@ -265,17 +276,6 @@ func (e *Engine) newCommand(ctx context.Context, extraEnv []string, tool types.T
265276 }
266277
267278 envvars , envMap := envAsMapAndDeDup (envvars )
268- for i , arg := range args {
269- args [i ] = os .Expand (arg , func (s string ) string {
270- return envMap [s ]
271- })
272- }
273-
274- // After we determined the interpreter we again interpret the args by env vars
275- args , err = replaceVariablesForInterpreter (interpreter , envMap )
276- if err != nil {
277- return nil , nil , err
278- }
279279
280280 if runtime .GOOS == "windows" && (args [0 ] == "/bin/bash" || args [0 ] == "/bin/sh" ) {
281281 args [0 ] = path .Base (args [0 ])
@@ -286,8 +286,7 @@ func (e *Engine) newCommand(ctx context.Context, extraEnv []string, tool types.T
286286 }
287287
288288 var (
289- cmdArgs = args [1 :]
290- stop = func () {}
289+ stop = func () {}
291290 )
292291
293292 if strings .TrimSpace (rest ) != "" {
@@ -305,105 +304,33 @@ func (e *Engine) newCommand(ctx context.Context, extraEnv []string, tool types.T
305304 stop ()
306305 return nil , nil , err
307306 }
308- cmdArgs = append (cmdArgs , f .Name ())
309- }
310-
311- // This is a workaround for Windows, where the command interpreter is constructed with unix style paths
312- // It converts unix style paths to windows style paths
313- if runtime .GOOS == "windows" {
314- parts := strings .Split (args [0 ], "/" )
315- if parts [len (parts )- 1 ] == "gptscript-go-tool" {
316- parts [len (parts )- 1 ] = "gptscript-go-tool.exe"
317- }
318-
319- args [0 ] = filepath .Join (parts ... )
307+ args = append (args , f .Name ())
320308 }
321309
322- cmd := exec .CommandContext (ctx , env .Lookup (envvars , args [0 ]), cmdArgs ... )
323- cmd .Env = compressEnv (envvars )
324- return cmd , stop , nil
325- }
326-
327- func replaceVariablesForInterpreter (interpreter string , envMap map [string ]string ) ([]string , error ) {
328- var parts []string
329- for i , part := range splitByQuotes (interpreter ) {
330- if i % 2 == 0 {
331- part = os .Expand (part , func (s string ) string {
310+ // Expand and/or normalize env references
311+ for i , arg := range args {
312+ args [i ] = os .Expand (arg , func (s string ) string {
313+ if strings .HasPrefix (s , "!" ) {
314+ return envMap [s [1 :]]
315+ }
316+ if ! useShell {
332317 return envMap [s ]
333- })
334- // We protect newly resolved env vars from getting replaced when we do the second Expand
335- // after shlex. Yeah, crazy. I'm guessing this isn't secure, but just trying to avoid a foot gun.
336- part = os .Expand (part , func (s string ) string {
337- return "${__" + s + "}"
338- })
339- }
340- parts = append (parts , part )
341- }
342-
343- parts , err := shlex .Split (strings .Join (parts , "" ))
344- if err != nil {
345- return nil , err
346- }
347-
348- for i , part := range parts {
349- parts [i ] = os .Expand (part , func (s string ) string {
350- if strings .HasPrefix (s , "__" ) {
351- return "${" + s [2 :] + "}"
352318 }
353- return envMap [ s ]
319+ return "${" + s + "}"
354320 })
355321 }
356322
357- return parts , nil
358- }
359-
360- // splitByQuotes will split a string by parsing matching double quotes (with \ as the escape character).
361- // The return value conforms to the following properties
362- // 1. s == strings.Join(result, "")
363- // 2. Even indexes are strings that were not in quotes.
364- // 3. Odd indexes are strings that were quoted.
365- //
366- // Example: s = `In a "quoted string" quotes can be escaped with \"`
367- //
368- // result = [`In a `, `"quoted string"`, ` quotes can be escaped with \"`]
369- func splitByQuotes (s string ) (result []string ) {
370- var (
371- buf strings.Builder
372- inEscape , inQuote bool
373- )
374-
375- for _ , c := range s {
376- if inEscape {
377- buf .WriteRune (c )
378- inEscape = false
379- continue
380- }
381-
382- switch c {
383- case '"' :
384- if inQuote {
385- buf .WriteRune (c )
386- }
387- result = append (result , buf .String ())
388- buf .Reset ()
389- if ! inQuote {
390- buf .WriteRune (c )
391- }
392- inQuote = ! inQuote
393- case '\\' :
394- inEscape = true
395- buf .WriteRune (c )
396- default :
397- buf .WriteRune (c )
398- }
323+ if runtime .GOOS == "windows" {
324+ args [0 ] = strings .ReplaceAll (args [0 ], "/" , "\\ " )
399325 }
400326
401- if buf .Len () > 0 {
402- if inQuote {
403- result = append (result , "" )
404- }
405- result = append (result , buf .String ())
327+ if useShell {
328+ args = append ([]string {"/bin/sh" , "-c" }, strings .Join (args , " " ))
329+ } else {
330+ args [0 ] = env .Lookup (envvars , args [0 ])
406331 }
407332
408- return
333+ cmd := exec .CommandContext (ctx , args [0 ], args [1 :]... )
334+ cmd .Env = compressEnv (envvars )
335+ return cmd , stop , nil
409336}
0 commit comments