11
11
#include " builtin.h"
12
12
#include " parser.h"
13
13
#include " expand.h"
14
+ #include " reader.h"
14
15
#include " wutil.h"
15
16
#include " exec.h"
16
17
#include " path.h"
@@ -47,7 +48,28 @@ node_offset_t parse_execution_context_t::get_offset(const parse_node_t &node) co
47
48
48
49
bool parse_execution_context_t::should_cancel_execution (const block_t *block) const
49
50
{
50
- return block && (block->skip || block->loop_status != LOOP_NORMAL);
51
+ return cancellation_reason (block) != execution_cancellation_none;
52
+ }
53
+
54
+ parse_execution_context_t ::execution_cancellation_reason_t parse_execution_context_t::cancellation_reason (const block_t *block) const
55
+ {
56
+ if (shell_is_exiting ())
57
+ {
58
+ return execution_cancellation_exit;
59
+ }
60
+ else if (block && block->loop_status != LOOP_NORMAL)
61
+ {
62
+ /* Nasty hack - break and continue set the 'skip' flag as well as the loop status flag. */
63
+ return execution_cancellation_loop_control;
64
+ }
65
+ else if (block && block->skip )
66
+ {
67
+ return execution_cancellation_skip;
68
+ }
69
+ else
70
+ {
71
+ return execution_cancellation_none;
72
+ }
51
73
}
52
74
53
75
int parse_execution_context_t::run_if_statement (const parse_node_t &statement)
@@ -229,17 +251,20 @@ int parse_execution_context_t::run_for_statement(const parse_node_t &header, con
229
251
230
252
this ->run_job_list (block_contents, fb);
231
253
232
- /* Handle break or continue */
233
- if (fb->loop_status == LOOP_CONTINUE)
234
- {
235
- /* Reset the loop state */
236
- fb->loop_status = LOOP_NORMAL;
237
- fb->skip = false ;
238
- continue ;
239
- }
240
- else if (fb->loop_status == LOOP_BREAK)
254
+ if (this ->cancellation_reason (fb) == execution_cancellation_loop_control)
241
255
{
242
- break ;
256
+ /* Handle break or continue */
257
+ if (fb->loop_status == LOOP_CONTINUE)
258
+ {
259
+ /* Reset the loop state */
260
+ fb->loop_status = LOOP_NORMAL;
261
+ fb->skip = false ;
262
+ continue ;
263
+ }
264
+ else if (fb->loop_status == LOOP_BREAK)
265
+ {
266
+ break ;
267
+ }
243
268
}
244
269
}
245
270
@@ -374,17 +399,20 @@ int parse_execution_context_t::run_while_statement(const parse_node_t &header, c
374
399
/* The block ought to go inside the loop (see #1212) */
375
400
this ->run_job_list (block_contents, wb);
376
401
377
- /* Handle break or continue */
378
- if (wb->loop_status == LOOP_CONTINUE)
402
+ if (this ->cancellation_reason (wb) == execution_cancellation_loop_control)
379
403
{
380
- /* Reset the loop state */
381
- wb->loop_status = LOOP_NORMAL;
382
- wb->skip = false ;
383
- continue ;
384
- }
385
- else if (wb->loop_status == LOOP_BREAK)
386
- {
387
- break ;
404
+ /* Handle break or continue */
405
+ if (wb->loop_status == LOOP_CONTINUE)
406
+ {
407
+ /* Reset the loop state */
408
+ wb->loop_status = LOOP_NORMAL;
409
+ wb->skip = false ;
410
+ continue ;
411
+ }
412
+ else if (wb->loop_status == LOOP_BREAK)
413
+ {
414
+ break ;
415
+ }
388
416
}
389
417
}
390
418
@@ -418,15 +446,129 @@ bool parse_execution_context_t::append_unmatched_wildcard_error(const parse_node
418
446
return append_error (unmatched_wildcard, WILDCARD_ERR_MSG, get_source (unmatched_wildcard).c_str ());
419
447
}
420
448
449
+ /* Handle the case of command not found */
450
+ void parse_execution_context_t::handle_command_not_found (const wcstring &cmd_str, const parse_node_t &statement_node, int err_code)
451
+ {
452
+ assert (statement_node.type == symbol_plain_statement);
453
+
454
+ /*
455
+ We couldn't find the specified command.
456
+
457
+ What we want to happen now is that the
458
+ specified job won't get executed, and an
459
+ error message is printed on-screen, but
460
+ otherwise, the parsing/execution of the
461
+ file continues. Because of this, we don't
462
+ want to call error(), since that would stop
463
+ execution of the file. Instead we let
464
+ p->actual_command be 0 (null), which will
465
+ cause the job to silently not execute. We
466
+ also print an error message and set the
467
+ status to 127 (This is the standard number
468
+ for this, used by other shells like bash
469
+ and zsh).
470
+ */
471
+
472
+ const wchar_t * const cmd = cmd_str.c_str ();
473
+ const wchar_t * const equals_ptr = wcschr (cmd, L' =' );
474
+ if (equals_ptr != NULL )
475
+ {
476
+ /* Try to figure out if this is a pure variable assignment (foo=bar), or if this appears to be running a command (foo=bar ruby...) */
421
477
478
+ const wcstring name_str = wcstring (cmd, equals_ptr - cmd); // variable name, up to the =
479
+ const wcstring val_str = wcstring (equals_ptr + 1 ); // variable value, past the =
480
+
481
+
482
+ const parse_node_tree_t ::parse_node_list_t args = tree.find_nodes (statement_node, symbol_argument, 1 );
483
+
484
+ if (! args.empty ())
485
+ {
486
+ const wcstring argument = get_source (*args.at (0 ));
487
+
488
+ wcstring ellipsis_str = wcstring (1 , ellipsis_char);
489
+ if (ellipsis_str == L" $" )
490
+ ellipsis_str = L" ..." ;
491
+
492
+ /* Looks like a command */
493
+ debug (0 ,
494
+ _ (L" Unknown command '%ls'. Did you mean to run %ls with a modified environment? Try 'env %ls=%ls %ls%ls'. See the help section on the set command by typing 'help set'." ),
495
+ cmd,
496
+ argument.c_str (),
497
+ name_str.c_str (),
498
+ val_str.c_str (),
499
+ argument.c_str (),
500
+ ellipsis_str.c_str ());
501
+ }
502
+ else
503
+ {
504
+ debug (0 ,
505
+ COMMAND_ASSIGN_ERR_MSG,
506
+ cmd,
507
+ name_str.c_str (),
508
+ val_str.c_str ());
509
+ }
510
+ }
511
+ else if (cmd[0 ]==L' $' || cmd[0 ] == VARIABLE_EXPAND || cmd[0 ] == VARIABLE_EXPAND_SINGLE)
512
+ {
513
+
514
+ const env_var_t val_wstr = env_get_string (cmd+1 );
515
+ const wchar_t *val = val_wstr.missing () ? NULL : val_wstr.c_str ();
516
+ if (val)
517
+ {
518
+ debug (0 ,
519
+ _ (L" Variables may not be used as commands. Instead, define a function like 'function %ls; %ls $argv; end' or use the eval builtin instead, like 'eval %ls'. See the help section for the function command by typing 'help function'." ),
520
+ cmd+1 ,
521
+ val,
522
+ cmd,
523
+ cmd);
524
+ }
525
+ else
526
+ {
527
+ debug (0 ,
528
+ _ (L" Variables may not be used as commands. Instead, define a function or use the eval builtin instead, like 'eval %ls'. See the help section for the function command by typing 'help function'." ),
529
+ cmd,
530
+ cmd);
531
+ }
532
+ }
533
+ else if (wcschr (cmd, L' $' ))
534
+ {
535
+ debug (0 ,
536
+ _ (L" Commands may not contain variables. Use the eval builtin instead, like 'eval %ls'. See the help section for the eval command by typing 'help eval'." ),
537
+ cmd,
538
+ cmd);
539
+ }
540
+ else if (err_code!=ENOENT)
541
+ {
542
+ debug (0 ,
543
+ _ (L" The file '%ls' is not executable by this user" ),
544
+ cmd?cmd:L" UNKNOWN" );
545
+ }
546
+ else
547
+ {
548
+ /*
549
+ Handle unrecognized commands with standard
550
+ command not found handler that can make better
551
+ error messages
552
+ */
553
+
554
+ wcstring_list_t event_args;
555
+ event_args.push_back (cmd_str);
556
+ event_fire_generic (L" fish_command_not_found" , &event_args);
557
+ }
558
+
559
+ /* Set the last proc status appropriately */
560
+ proc_set_last_status (err_code==ENOENT?STATUS_UNKNOWN_COMMAND:STATUS_NOT_EXECUTABLE);
561
+ }
422
562
423
563
/* Creates a 'normal' (non-block) process */
424
564
process_t *parse_execution_context_t ::create_plain_process(job_t *job, const parse_node_t &statement)
425
565
{
566
+ assert (statement.type == symbol_plain_statement);
567
+
426
568
bool errored = false ;
427
569
428
- /* Get the decoration */
429
- assert (statement. type == symbol_plain_statement) ;
570
+ /* We may decide that a command should be an implicit cd */
571
+ bool use_implicit_cd = false ;
430
572
431
573
/* Get the command. We expect to always get it here. */
432
574
wcstring cmd;
@@ -442,28 +584,7 @@ process_t *parse_execution_context_t::create_plain_process(job_t *job, const par
442
584
443
585
if (errored)
444
586
return NULL ;
445
-
446
- /* The list of arguments. The command is the first argument. TODO: count hack */
447
- const parse_node_t *unmatched_wildcard = NULL ;
448
- wcstring_list_t argument_list = this ->determine_arguments (statement, &unmatched_wildcard);
449
- argument_list.insert (argument_list.begin (), cmd);
450
-
451
- /* If we were not able to expand any wildcards, here is the first one that failed */
452
- if (unmatched_wildcard != NULL )
453
- {
454
- job_set_flag (job, JOB_WILDCARD_ERROR, 1 );
455
- errored = append_unmatched_wildcard_error (*unmatched_wildcard);
456
- }
457
-
458
- if (errored)
459
- return NULL ;
460
-
461
- /* The set of IO redirections that we construct for the process */
462
- io_chain_t process_io_chain;
463
- errored = ! this ->determine_io_chain (statement, &process_io_chain);
464
- if (errored)
465
- return NULL ;
466
-
587
+
467
588
/* Determine the process type, which depends on the statement decoration (command, builtin, etc) */
468
589
enum parse_statement_decoration_t decoration = tree.decoration_for_plain_statement (statement);
469
590
enum process_type_t process_type = EXTERNAL;
@@ -500,15 +621,71 @@ process_t *parse_execution_context_t::create_plain_process(job_t *job, const par
500
621
wcstring actual_cmd;
501
622
if (process_type == EXTERNAL)
502
623
{
503
- /* Determine the actual command. Need to support implicit cd here */
624
+ /* Determine the actual command. This may be an implicit cd. */
504
625
bool has_command = path_get_path (cmd, &actual_cmd);
505
626
506
- if (! has_command)
627
+ /* If there was no command, then we care about the value of errno after checking for it, to distinguish between e.g. no file vs permissions problem */
628
+ const int no_cmd_err_code = errno;
629
+
630
+ /* If the specified command does not exist, and is undecorated, try using an implicit cd. */
631
+ if (! has_command && decoration == parse_statement_decoration_none)
507
632
{
508
- /* TODO: support fish_command_not_found, implicit cd, etc. here */
633
+ /* Implicit cd requires an empty argument and redirection list */
634
+ const parse_node_t *args = get_child (statement, 1 , symbol_arguments_or_redirections_list);
635
+ if (args->child_count == 0 )
636
+ {
637
+ /* Ok, no arguments or redirections; check to see if the first argument is a directory */
638
+ wcstring implicit_cd_path;
639
+ use_implicit_cd = path_can_be_implicit_cd (cmd, &implicit_cd_path);
640
+ }
641
+ }
642
+
643
+ if (! has_command && ! use_implicit_cd)
644
+ {
645
+ /* No command */
646
+ this ->handle_command_not_found (cmd, statement, no_cmd_err_code);
509
647
errored = true ;
510
648
}
511
649
}
650
+ if (errored)
651
+ return NULL ;
652
+
653
+ /* The argument list and set of IO redirections that we will construct for the process */
654
+ wcstring_list_t argument_list;
655
+ io_chain_t process_io_chain;
656
+ if (use_implicit_cd)
657
+ {
658
+ /* Implicit cd is simple */
659
+ argument_list.push_back (L" cd" );
660
+ argument_list.push_back (cmd);
661
+ actual_cmd.clear ();
662
+
663
+ /* If we have defined a wrapper around cd, use it, otherwise use the cd builtin */
664
+ process_type = function_exists (L" cd" ) ? INTERNAL_FUNCTION : INTERNAL_BUILTIN;
665
+ }
666
+ else
667
+ {
668
+ /* Form the list of arguments. The command is the first argument. TODO: count hack */
669
+ const parse_node_t *unmatched_wildcard = NULL ;
670
+ argument_list = this ->determine_arguments (statement, &unmatched_wildcard);
671
+ argument_list.insert (argument_list.begin (), cmd);
672
+
673
+ /* If we were not able to expand any wildcards, here is the first one that failed */
674
+ if (unmatched_wildcard != NULL )
675
+ {
676
+ job_set_flag (job, JOB_WILDCARD_ERROR, 1 );
677
+ errored = append_unmatched_wildcard_error (*unmatched_wildcard);
678
+ }
679
+
680
+ if (errored)
681
+ return NULL ;
682
+
683
+ /* The set of IO redirections that we construct for the process */
684
+ errored = ! this ->determine_io_chain (statement, &process_io_chain);
685
+ if (errored)
686
+ return NULL ;
687
+ }
688
+
512
689
513
690
/* Return the process, or NULL on error */
514
691
process_t *result = NULL ;
@@ -953,7 +1130,7 @@ int parse_execution_context_t::run_1_job(const parse_node_t &job_node, const blo
953
1130
profile_item->skipped = process_errored;
954
1131
}
955
1132
956
- /* Set the last status to 1 if the job could not be executed */
1133
+ /* Set the last status to 1 if the job could not be executed. TODO: Don't stomp STATUS_UNKNOWN_COMMAND / STATUS_NOT_EXECUTABLE */
957
1134
if (process_errored)
958
1135
proc_set_last_status (1 );
959
1136
const int ret = proc_get_last_status ();
0 commit comments