@@ -11,13 +11,11 @@ import (
11
11
"os"
12
12
"os/exec"
13
13
"path"
14
- "path/filepath"
15
14
"runtime"
16
15
"sort"
17
16
"strings"
18
17
"sync"
19
18
20
- "github.com/google/shlex"
21
19
"github.com/gptscript-ai/gptscript/pkg/counter"
22
20
"github.com/gptscript-ai/gptscript/pkg/env"
23
21
"github.com/gptscript-ai/gptscript/pkg/types"
@@ -254,28 +252,14 @@ func (e *Engine) newCommand(ctx context.Context, extraEnv []string, tool types.T
254
252
interpreter , rest , _ := strings .Cut (tool .Instructions , "\n " )
255
253
interpreter = strings .TrimSpace (interpreter )[2 :]
256
254
257
- args , err := shlex .Split (interpreter )
258
- if err != nil {
259
- return nil , nil , err
260
- }
255
+ args := strings .Fields (interpreter )
261
256
262
- envvars , err = e .getRuntimeEnv (ctx , tool , args , envvars )
257
+ envvars , err : = e .getRuntimeEnv (ctx , tool , args , envvars )
263
258
if err != nil {
264
259
return nil , nil , err
265
260
}
266
261
267
262
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
- }
279
263
280
264
if runtime .GOOS == "windows" && (args [0 ] == "/bin/bash" || args [0 ] == "/bin/sh" ) {
281
265
args [0 ] = path .Base (args [0 ])
@@ -286,8 +270,7 @@ func (e *Engine) newCommand(ctx context.Context, extraEnv []string, tool types.T
286
270
}
287
271
288
272
var (
289
- cmdArgs = args [1 :]
290
- stop = func () {}
273
+ stop = func () {}
291
274
)
292
275
293
276
if strings .TrimSpace (rest ) != "" {
@@ -305,105 +288,30 @@ func (e *Engine) newCommand(ctx context.Context, extraEnv []string, tool types.T
305
288
stop ()
306
289
return nil , nil , err
307
290
}
308
- cmdArgs = append (cmdArgs , f .Name ())
291
+ args = append (args , f .Name ())
309
292
}
310
293
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 ... )
320
- }
321
-
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 {
332
- 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 :] + "}"
352
- }
353
- return envMap [s ]
354
- })
355
- }
356
-
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 )
294
+ // translate UNIX style to Windows
295
+ for i , arg := range args {
296
+ args [i ] = os .Expand (arg , func (s string ) string {
297
+ if strings .HasPrefix (s , "!" ) {
298
+ return envMap [s [1 :]]
386
299
}
387
- result = append (result , buf .String ())
388
- buf .Reset ()
389
- if ! inQuote {
390
- buf .WriteRune (c )
300
+ if runtime .GOOS == "windows" {
301
+ return "%" + s + "%"
391
302
}
392
- inQuote = ! inQuote
393
- case '\\' :
394
- inEscape = true
395
- buf .WriteRune (c )
396
- default :
397
- buf .WriteRune (c )
398
- }
303
+ return "${" + s + "}"
304
+ })
399
305
}
400
306
401
- if buf . Len () > 0 {
402
- if inQuote {
403
- result = append (result , "" )
404
- }
405
- result = append (result , buf . String ( ))
307
+ if runtime . GOOS == "windows" {
308
+ args [ 0 ] = strings . ReplaceAll ( args [ 0 ], "/" , " \\ " )
309
+ args = append ([] string { "cmd.exe" , "/c" }, strings . Join ( args , " " ) )
310
+ } else {
311
+ args = append ([] string { "/bin/sh" , "-c" }, strings . Join ( args , " " ))
406
312
}
407
313
408
- return
314
+ cmd := exec .CommandContext (ctx , args [0 ], args [1 :]... )
315
+ cmd .Env = compressEnv (envvars )
316
+ return cmd , stop , nil
409
317
}
0 commit comments