michaelrodriguess / mini-bash

0 stars 0 forks source link

Tokenization / Lexer #26

Closed FernandaMatt closed 1 year ago

FernandaMatt commented 1 year ago

Todo

cat Makefile |. wc -l. >> " temp.txt ' " (0) (0). (1) (0). (0) (2) (3) (0) (4) (3)

0 -> word 1 -> pipe 2 -> redirect

English

Work in Progress

Example

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

struct Token {
  int type;
  char *word;
  struct Token *next;
};

struct Token *lexer(const char *input) {
  struct Token *head = NULL;
  struct Token *current = NULL;

  int len = strlen(input);
  int i = 0;
  while (i < len) {
    int type = 0;
    int start = i;
    int end = i;

    if (input[i] == '|') {
      type = 1;
      end = ++i;
    } else if (input[i] == '>' || input[i] == '<') {
      type = 2;
      end = ++i;
    } else if (input[i] == '\"' || input[i] == '\'') {
      type = 3;
      start = ++i;
      char quote = input[start - 1];
      while (i < len && input[i] != quote) {
        i++;
      }
      end = i;
      if (i < len) {
        i++;
      }
    } else {
      while (i < len && input[i] != '|' && input[i] != '>' && input[i] != '<' && input[i] != '\"' && input[i] != '\'') {
        i++;
      }
      end = i;
    }

    struct Token *token = (struct Token *)malloc(sizeof(struct Token));
    token->type = type;
    token->word = (char *)malloc(end - start + 1);
    memcpy(token->word, input + start, end - start);
    token->word[end - start] = '\0';
    token->next = NULL;

    if (head == NULL) {
      head = token;
    } else {
      current->next = token;
    }
    current = token;
  }

  return head;
}

void print_tokens(const struct Token *head) {
  while (head != NULL) {
    printf("Type: %d, Word: %s\n", head->type, head->word);
    head = head->next;
  }
}

int main() {
  const char *input = "ls -l | grep \"hello world\" > output.txt";
  struct Token *head = lexer(input);
  print_tokens(head);
  return 0;
}

Português

Work in Progress

A função tokenize recebe como entrada uma string e retorna uma lista de tokens, onde cada token tem um tipo (palavra, pipe, redirecionamento ou aspas) e um valor (o conteúdo da palavra, pipe, redirecionamento ou aspas).

Este código é um exemplo de um lexer simples escrito em C, que lê uma sequência de entrada de caracteres e produz uma lista de tokens com informações sobre o tipo e a palavra correspondente a cada token.

O processo de criação do código envolve a definição de uma estrutura de dados de token, a implementação de uma função de lexer para analisar a entrada de caracteres e gerar os tokens correspondentes, e uma função de impressão de tokens para exibir os resultados na saída.

A função de lexer usa um loop para percorrer a entrada de caracteres, identificar o tipo de cada token com base em regras simples (como caracteres especiais ou aspas), criar um novo token para cada sequência de caracteres que corresponde a um token, e encadear os tokens na lista de tokens.

A função de impressão de tokens apenas percorre a lista de tokens e imprime as informações de tipo e palavra para cada token na saída.

FernandaMatt commented 1 year ago

Special characters functionalities and behavior

From the bash manual:

3.1.2.2 Single Quotes Enclosing characters in single quotes (') preserves the literal value of each character within the quotes. A single quote may not occur between single quotes, even when preceded by a backslash. 3.1.2.3 Double Quotes Enclosing characters in double quotes (") preserves the literal value of all characters within the quotes, with the exception of $, `, \, and, when history expansion is enabled, !. The characters $ and ` retain their special meaning within double quotes (see Shell Expansions). The backslash retains its special meaning only when followed by one of the following characters: $, `, ", \, or newline. Within double quotes, backslashes that are followed by one of these characters are removed. Backslashes preceding characters without a special meaning are left unmodified. A double quote may be quoted within double quotes by preceding it with a backslash. If enabled, history expansion will be performed unless an ! appearing in double quotes is escaped using a backslash. The backslash preceding the ! is not removed.

In summary, single quotes always maintain the literal values of the characters enclosed, no matter what. Meanwhile double quotes don't preserve the literal values of $, `, \, and, when history expansion is enabled, ! (but we don't need to handle the characters that are not in the list of special characters mentioned in the subject). We can use \ scapes with double, but not with single quotes, the \ will only function as a scape, if it is followed by a special character, otherwise it will maintain it's litteral value.

:brazil:

Em resumo, os caracteres em asplas simples sempre manterão seu valor literal, já as aspas duplas não preservarão o valor dos caracteres especiais $, `, \ e ! quando a expansão de histórico estiver habilitada (mas só precisamos nos preocupar com os caracteres exigidos no subject list). Por exemplo, nós podemos acessar o valor de variáveis de ambiente com $ENVAR entre aspas duplas, ou usar scape com \, mas entre aspas simples, não, eles manterão seu valor literal.

Some bash tests:

feffa@fcaetano42:~$ echo "test $PATH"
test /usr/local/bin:/usr/bin:/bin:/usr/games

feffa@fcaetano42:~$ echo 'test $PATH'
test $PATH

feffa@fcaetano42:~$ echo 'test print a single quote ' between single quotes'
> ...
> heredoc
> ... '
test print a single quote  between single quotes
...
heredoc
... 

feffa@fcaetano42:~$ echo "test print a single quote ' between double quotes"
test print a single quote ' between double quotes

feffa@fcaetano42:~$ echo "test print a double quote " between double quotes"
> ...
> heredoc
> ... "
test print a double quote  between double quotes
...
heredoc
... 

feffa@fcaetano42:~$ echo "test print a double quote scaped \" between double quotes"
test print a double quote scaped " between double quotes

feffa@fcaetano42:~$ echo "test print a single quote scaped \' between double quotes"
test print a single quote scaped \' between double quotes

feffa@fcaetano42:~$ echo 'test print a single quote scaped \' between single quotes'
> ...
> heredoc
> ... '
test print a single quote scaped \ between single quotes
...
heredoc
... 

feffa@fcaetano42:~$ echo 'test print a single quote in double quotes "'" between single quotes'
> ...
> heredoc
> ... '
> ... "
test print a single quote in double quotes " between single quotes'
...
heredoc
... '
... 

feffa@fcaetano42:~$ echo "test print a double quote in single quotes '"' between double quotes"
> ...
> heredoc
> ... '
test print a double quote in single quotes ' between double quotes"
...
heredoc
... 

A pipe in Bash takes the standard output of one process and passes it as standard input into another process.

:brazil:

Um pipe redirecionará a saída padrão de um processo e passará isso como entrada para um outro processo.

Some bash tests:

feffa@fcaetano42:~$ echo "test pipe" | cat > test.txt
feffa@fcaetano42:~$ cat test.txt
test pipe

feffa@fcaetano42:~$ ls | wc -l
11

feffa@fcaetano42:~$ ls|wc -l
11

feffa@fcaetano42:~$ ls |wc -l
11

feffa@fcaetano42:~$ ls| wc -l
11

feffa@fcaetano42:~$ ls     | wc -l
11

feffa@fcaetano42:~$ ls
42       Documents  mini-bash  Pictures  Templates  Videos
Desktop  Downloads  Music      Public    test.txt

feffa@fcaetano42:~$ ls | grep D | wc -l
3

feffa@fcaetano42:~$ ls | grep D| wc -l
3

feffa@fcaetano42:~$ ls |grep D| wc -l
3

feffa@fcaetano42:~$ ls |grep D|wc -l
3

feffa@fcaetano42:~$ ls|grep D|wc -l
3

feffa@fcaetano42:~$ ls|grep D       |wc -l
3

feffa@fcaetano42:~$ ls | grep D |
> heredoc ...
bash: heredoc: command not found

:warning: Unlike flags and most command and functions inputs/arguments, pipes | work with "wrong spacing", as seen in the tests above. See the behavior in the tests. When a | is at the end of the command line, heredoc starts for the user to complete the command. When a | is at the beginning of a command line, bash returns a syntax error. If the first command of a pipe line fails it doesn't stop the execution of the rest of the line.

:brazil:

:warning: Diferente de flags e inputs/argumentos de funções e comandos que são separados no terminal por espaço, os pipes são reconhecidos independente disso. Observar nos testes. Quando um pipe encerra uma linha de comando, o heredoc é iniciado para esperar que o usuário entre com a próxima instrução. Quando um | está no início de uma linha de comando, o bash retorna um erro de sintaxe. Se o primeiro comando de uma sequência de pipes falha, isso não interrompe a execução do restante da sequência.

feffa@fcaetano42:~/42/projetos$ echo test
test
feffa@fcaetano42:~/42/projetos$ | echo test
bash: syntax error near unexpected token `|'
feffa@fcaetano42:~/42/projetos$ |echo test
bash: syntax error near unexpected token `|'
feffa@fcaetano42:~/42/projetos$      | echo test
bash: syntax error near unexpected token `|'
feffa@fcaetano42:~/42/projetos$ | echo test
bash: syntax error near unexpected token `|'
feffa@fcaetano42:~/42/projetos$ a | echo test
test
bash: a: command not found

>:

This symbol, known as the file redirection operator, is typically used to redirect the contents of a command/file to another by overwriting it. Mind you; it overwrites it !

:brazil:

Símbolo de redirecionamento de arquivo, normalmente é usado para redirecionar o conteúdo de um arquivo ou comando para outro, sobrescrevendo o destino.

feffa@fcaetano42:~$ cat test.txt 
test pipe
feffa@fcaetano42:~$ echo "test redirects" > test.txt 
feffa@fcaetano42:~$ cat test.txt 
test redirects

When we write command > file.txt it is equivalent to writing command 1> file.txt. The number is the file descriptor from the file we are redirecting. 1 is the default and is the standard output (0 is standard input, and 2 standard error). But we can redirect from the standard error as well.

:brazil:

Quando escrevemos command > file.txt é o mesmo que escrevermos command 1> file.txt. O número é o file descriptor do arquivo que estamos redirecionando. 1 é o padrão e é o fd do standard output (saída padrão) (0 é standard input e 2 standard error) Nós podemos redirecionar a saída do standard error, ao invés do standard output usando 2>.

feffa@fcaetano42:~$ mcat test.txt 1> test2.txt 
bash: mcat: command not found
feffa@fcaetano42:~$ mcat test.txt > test2.txt 
bash: mcat: command not found
feffa@fcaetano42:~$ mcat test.txt 2> test2.txt 
feffa@fcaetano42:~$ cat test2.txt 
bash: mcat: command not found

<:

The symbol < is used for input redirection. Files, for example, can be used as input. However, in this case, the input redirection is a read-only redirection.

:brazil:

O símbolo < é usado para redirecionamento de input. Podemos, por exemplo, usar arquivos como inputs. No entanto, nesse caso o redirecionamento da entrada/arquivo é reado-only, ou seja, apenas escrita, não temos permissão de alterar o arquivo.

feffa@fcaetano42:~$ cat < test.txt 
test redirects 2
//In this case, the file.txt is taken as the input, and the cat command then cats it out.

>>:

The append >> operator adds the output to the existing content instead of overwriting it. This allows you to redirect the output from multiple commands to a single file.

:brazil:

O operador append >>, funciona também para redirecionamento de output, como o >, no entanto, ele adiciona o output ao final do arquivo, ao invés de sobrescrever seu conteúdo. Isso permite que se faça o redirecionamento de vários comandos consecutivos em um único arquivo.

feffa@fcaetano42:~$ date > test.txt
feffa@fcaetano42:~$ cat test.txt 
Fri 17 Feb 2023 09:15:08 PM -03
feffa@fcaetano42:~$ hostname >> test.txt 
feffa@fcaetano42:~$ uname -r >> test.txt 
feffa@fcaetano42:~$ cat test.txt 
Fri 17 Feb 2023 09:15:08 PM -03
fcaetano42
5.10.0-21-amd64

<<: Use: <<MARKER Known as the here-document redirection operator. This operator instructs bash to read the input from stdin until a line containing only the delimiter MARKER is found. At this point bash passes the all the input read so far to the stdin of the command.

:brazil:

Uso: << MARKER Conhecido como operador de redirecionamento de here-document. Esse operador instrui o bash a ler a entrada até que uma linha contendo somente o delimitador MARKER seja inserida. Nesse momento o bash reconhece o final da linha de comando e a mesma é repassada como entrada do comando.

feffa@fcaetano42:~$ sed 's|http://||' http://url1.com
sed: can't read http://url1.com: No such file or directory
feffa@fcaetano42:~$ sed 's|http://||' <<EOL
> http://url1.com
> EOL
url1.com
feffa@fcaetano42:~$

When the delimiter is quoted no parameter expansion and/or command substitution is done by the shell. (Not sure if we need to implement that in our mini-shell)

:brazil:

Quando o delimitador está entre aspas, nenhuma expansão de parametros e/ou comandos de substituição é feito pela shell. (Não sei se precisamos implementar isso)

feffa@fcaetano42:~$ cat <<STOP
> Current directory: $PWD
> Current user: $(whoami)
> STOP
Current directory: /home/feffa
Current user: feffa
feffa@fcaetano42:~$ cat <<"STOP"
> Current directory: $PWD
> Current user: $(whoami)
> STOP
Current directory: $PWD
Current user: $(whoami)

:question: Is better to create a separate token to >> and <<, or to handle two < or > consecutive tokens after? I think I would create a separate token, but it's worth to ask a few people and see what is better.

:warning: Unlike flags and most command and functions inputs/arguments, redirects work with "wrong spacing", as seen in the tests above. See the behavior in the tests. When a redirect is at the end of a command line, the shell returns a syntax error.

:brazil:

:question: É melhor criar um token separado para >> e <<, ou tratar dois < or > tokens consecutivos depois no parser? A princípio me parece melhor criar os tokens, mas talvez seja melhor trocar uma ideia com quem já fez a tokenização.

:warning: Diferente de flags e inputs/argumentos de funções e/ou comandos que são separados no terminal por espaço, os redirects são reconhecidos independente disso. Observar nos testes. Quando um redirect encerra uma linha de comando, a shell retorna um erro de sintaxe.

feffa@fcaetano42:~/42/projetos$ cat $HOSTNAME
cat: fcaetano42: No such file or directory
feffa@fcaetano42:~/42/projetos$ cat $HOSTNAME >
bash: syntax error near unexpected token `newline'
feffa@fcaetano42:~/42/projetos$ cat $HOSTNAME <
bash: syntax error near unexpected token `newline'
feffa@fcaetano42:~/42/projetos$ cat $HOSTNAME <<
bash: syntax error near unexpected token `newline'
feffa@fcaetano42:~/42/projetos$ cat $HOSTNAME >>
bash: syntax error near unexpected token `newline'

The $ is a special character that can be used with variables to expand their values. In the mini-shell project we will implement the $ functionalities to handle environment variables and the $? which should expand to the exit status of the most recently executed foreground pipe line. The command env shows the environment variables.

🇧🇷

O caractere especial $ pode ser usado para retornar o valor de variáveis, expandido-as ao seu valor em uma cadeia de string, por exemplo. No projeto da mini-shell nós iremos implementar a funcionalidade desse caractere para lidar com variáveis de ambiente e com $?, que deve retornar o status de saída (exit status) do pipe mais recente executado em primeiro plano.

$ENVVAR:

In the case of using $ with enviroment variables $ENVVAR should expand to the value of the environment variable. The command env can be used to show the environment variables.

🇧🇷

No uso de $ com variáveis de ambiente, a cadeia $ENVVAR deveria expandir para o valor da variável de ambiente ENVVAR. O comando env pode ser usado para mostrar as variáveis de ambiente.

feffa@fcaetano42:~$ $OLDPWD
bash: /home/feffa/42/projetos/mini-bash: Is a directory
feffa@fcaetano42:~$ echo $USER > test2.txt 
feffa@fcaetano42:~$ cat test2.txt 
feffa
feffa@fcaetano42:~$ echo $HOME >> test2.txt 
feffa@fcaetano42:~$ cat test2.txt 
feffa
/home/feffa

$?: It should expand to the exit status of the most recently executed foreground pipe line. The exit status of a command is an integer number returned by each Linux command when it terminates normally or abnormally. 0 exit status means the command was successful without any errors, and a non-zero (1-255 values) exit status means command was failure. The Linux man pages stats the exit statuses of each command. The special shell variable $? can be used to get the exit status of the previously executed command.

🇧🇷

Deve retornar o status de saída (exit status) do pipe mais recente executado em primeiro plano. O status de saída de um comando é número inteiro retornado por cada comando comando Linux quando o mesmo se encerra, de forma normal ou abrupta. O status 0 significa que o comando terminou a execução com sucesso, sem erros, enquanto um status diferente de 0 (entre 1 e 255), significa que houve alguma falha ou comportamento inesperado durante a execução. Os manuais do Linux especificam o status de saída para cada comando. Para exibir o status de saída do último comando, podemos utilizar $?.

feffa@fcaetano42:~$ echo test
test
feffa@fcaetano42:~$ $?
bash: 0: command not found
feffa@fcaetano42:~$ non-existing-command
bash: non-existing-command: command not found
feffa@fcaetano42:~$ $?
bash: 127: command not found
feffa@fcaetano42:~$ true
feffa@fcaetano42:~$ $?
bash: 0: command not found
feffa@fcaetano42:~$ false
feffa@fcaetano42:~$ $?
bash: 1: command not found
feffa@fcaetano42:~$ true | false
feffa@fcaetano42:~$ $?
bash: 1: command not found
feffa@fcaetano42:~$ false | true
feffa@fcaetano42:~$ $?
bash: 0: command not found

//0: succesfully/true exit status
//1: false exit status
//127: command not found exit status

There is an internal Bash variable called PIPESTATUS; it’s an array that holds the exit status of each command in your last foreground pipeline of commands.

🇧🇷

Existe uma variável interna do Bash chamada PIPESTATUS; é um array que guarda as informações do status de saída de cada comando da sua última linha de pipes executada em primeiro plano.

feffa@fcaetano42:~$ false | true | false | non-existing-command
bash: non-existing-command: command not found
feffa@fcaetano42:~$ echo "${PIPESTATUS[0]} ${PIPESTATUS[1]} ${PIPESTATUS[2]} ${PIPESTATUS[3]}"
1 0 1 127

References

https://www.baeldung.com/linux/pipe-output-to-function https://stackoverflow.com/questions/6697753/difference-between-single-and-double-quotes-in-bash https://www.gnu.org/software/bash/manual/html_node/Quoting.html https://unix.stackexchange.com/questions/503013/what-is-the-difference-between-the-and-quotes-in-th https://linuxhint.com/bash_pipe_tutorial/ https://www.redhat.com/sysadmin/redirect-operators-bash https://www.gnu.org/software/bash/manual/html_node/Redirections.html https://linuxhint.com/redirection-operators-bash/ https://linuxize.com/post/bash-heredoc/ https://bash.cyberciti.biz/guide/The_exit_status_of_a_command https://www.cyberciti.biz/faq/unix-linux-bash-find-out-the-exit-codes-of-all-piped-commands/ https://unix.stackexchange.com/questions/14270/get-exit-status-of-process-thats-piped-to-another/73180#73180 https://stackoverflow.com/questions/1221833/pipe-output-and-capture-exit-status-in-bash