After a few days of research, I have come up with a grammar for the minishell project.
list -> pipeline (";" | "&" | "&&" | "||" pipeline)* [";"] | ["&"] ["\n"]
| "(" list ")";
pipeline -> command (( "|" | "|&" | ";" | "&&" | "||" ) command)* ;
| "(" list ")";
command -> simple_command
| builtin
| DLESS
| redirection
| [time [-p]] [!] expression
| "(" list ")";
simple_command -> name (args)* ;
builtin -> name (args)* ;
redirection -> expression ( "<" | ">" | ">>" | "2>" | "&>" | "&>>" | "2>>" | "<>" | ">|") expression;
DLESS -> expression "<<" delimiter newline content delimiter;
delimiter -> STRING;
content -> MULTIPLE_LINE_TEXT;
flags -> FLAGS;
name -> WORD | COM_EXPANSION | VAR_EXPANSION;
args -> FLAGS | WORD | STRING | QUOTED_STRING | SIMPLE_QUOTED_STRING | VAR_EXPANSION | EXPR_EXPANSION;
The operators “&&” and “ | ” shall have equal precedence and shall be evaluated with left associativity. For example, both of the following commands write solely bar to standard output: |
false && echo foo || echo bar # false && echo foo is false so echo bar is executed
true || echo foo && echo bar # true || echo foo is true so echo bar is executed
For this subject:
<
: Redirects standard input from a file.>
: Redirects standard output to a file, overwriting the file if it exists.>>
: Redirects standard output to a file, appending to the file if it exists.<<
: Here DocumentThere are a few more redirection operators that you might encounter but we will not need to implement them:
2>
: Redirects standard error to a file, overwriting the file if it exists.&>
: Redirects both standard output and standard error to a file, overwriting the file if it exists.2>>
: Redirects standard error to a file, appending to the file if it exists.<>
: Opens a file for both reading and writing.>|
: Redirects standard output to a file, overwriting the file even if the noclobber option has been set in the shell.&>>
: Redirects both standard output and standard error to a file, appending to the file if it exists.We will implement a parser method called “recursive descent,” which is a top-down parser.
In bash, a delimiter is a character or a set of characters that separates different parts of the command line. The delimiters you’ve listed are a good start, but bash has a few more. Here’s an expanded list:
' '
)'\t'
)'\n'
);
)|
)&
)<
)>
)(
))
){
)}
)[
)]
)$
)`
)"
)'
)\
)=
)+
)-
)*
)/
),
)!
)~
)^
)%
)They have specific meanings:
{}
: Curly braces are used in bash for variable expansion (${variable}
), brace expansion ({1..10}
), and to define blocks of code (like in if statements and functions).[]
: Square brackets are used in bash for array indexing (array[0]
), and to test conditions ([ $a -lt 10 ]
or [[ $a -lt 10 ]]
).Here are some examples:
echo ${variable}
echo {1..10}
if [ $a -lt 10 ]; then
echo "a is less than 10"
fi
echo ${array[0]}
if [ $a -lt 10 ]
or if [[ $a -lt 10 ]]
./myscript arg1 arg2 arg3
Then inside myscript, $#
will be 3, because three arguments were passed to the script.
The ^
symbol in bash has a few different uses:
^
is used to denote the start of a line. For example, ^abc
matches any line that starts with “abc”.${var^}
converts the first character of $var
to uppercase.${var^^}
converts all characters of $var
to uppercase.tr
command, ^
is used to denote a range of characters. For example, tr A-Z a-z
converts uppercase letters to lowercase.tr
command, ^
is used to complement a set of characters when it’s the first character in a set. For example, tr -d '^0-9'
deletes all characters that are not digits.diff
command, ^
is used to denote lines that are different between two files.The <>
operator in bash is used for opening a file in read-write mode. Here’s an example:
command <> file
This command will run command
, with file
opened in read-write mode on standard input.
command 1>>file 2>&1
This command will run command
, and append both the stdout and stderr to file
.
Bash 4 and later shortened to:
command &>>file
No, the symbols ;;
, ;&
, and ;;&
cannot be at the beginning of a command line in bash.
These symbols are used in the context of a case statement in bash scripting:
;;
is used to end each case in a case statement.;&
allows execution to continue with the next case clause, rather than exiting the case statement.;;&
allows the shell to test the next pattern list in the case statement.Here’s an example of how they might be used:
case "$variable" in
pattern1)
command1
;;
pattern2)
command2
;&
pattern3)
command3
;;&
*)
default_command
;;
esac
A control operator in bash is one of those ||
, &&
, &
, ;
, ;;
, ;&
, ;;&
, |
, |&
, (
, or )
These control operators do have precedence and associativity rules, similar to operators in programming languages. Here’s a rough breakdown:
&&
and ||
have the same precedence and are left-associative. They allow you to execute a command based on the success (&&
) or failure (||
) of the previous command.;
and &
have the same precedence, which is lower than &&
and ||
. They allow you to separate commands (;
) or run a command in the background (&
).|
and |&
have higher precedence than &&
, ||
, ;
, and &
. They allow you to create pipelines, where the output of one command is used as the input of the next command (|
), or where both the output and error output of one command are used as the input of the next command (|&
).(
and )
can be used to group commands, which can override the default precedence rules.;;
, ;&
, and ;;&
are used in the context of a case statement to separate different cases.For wildcard expansion, you would typically use the glob function, as I mentioned in the previous response. Here’s how you can modify your code to expand wildcards in the input:
For example, if a user types ls *.txt
, the shell should expand the *.txt
wildcard to a list of all .txt
files in the current directory.
#include <glob.h>
// ...
char *input;
input = readline(" splash 💦 > ");
while (input != NULL)
{
add_input_to_history(input);
glob_t glob_result;
memset(&glob_result, 0, sizeof(glob_result));
// Expand wildcards in the input
if (glob(input, GLOB_TILDE, NULL, &glob_result) == 0) {
for (int i = 0; i < glob_result.gl_pathc; ++i) {
// Replace the input line with the expanded wildcard
rl_replace_line(glob_result.gl_pathv[i], 0);
// Redraw the input line
rl_redisplay();
}
} else {
ft_printf("You entered: %s\n", input);
}
globfree(&glob_result);
input = readline(" splash 💦 > ");
}
free(input);