C: интерпретатор shell

Вопросы написания собственного программного кода (на любых языках)

Модератор: Olej

Аватара пользователя
Olej
Писатель
Сообщения: 21338
Зарегистрирован: 24 сен 2011, 14:22
Откуда: Харьков
Контактная информация:

C: интерпретатор shell

Непрочитанное сообщение Olej » 21 дек 2016, 11:53

Задача: реализовать упрощённый командный интерпретатор shell.
В элементарном виде: принимать и выполнять команды - задача описана во множестве книг ... только ленивый не описывал её по программированию в Linux.

Но!
Задача стоит реализовать дополнительные возможности языка shell, такие как:

- конвейеры ... типа:

Код: Выделить всё

$ echo 12345 | cat | cat | cat | cat
- перенаправление ввода-вывода ... типа:

Код: Выделить всё

$ cat <f1 >f2
$ cat <f1 >>f2
- запуск в фоновом режиме ... типа:

Код: Выделить всё

$ prog &
[14567]


- так, чтобы фоновые программы после завершения не оставляли зомби...

Ну, может, и ещё какие изыски :lol:

Аватара пользователя
Olej
Писатель
Сообщения: 21338
Зарегистрирован: 24 сен 2011, 14:22
Откуда: Харьков
Контактная информация:

Re: C: интерпретатор shell

Непрочитанное сообщение Olej » 21 дек 2016, 12:38

Olej писал(а): В элементарном виде: принимать и выполнять команды - задача описана во множестве книг ... только ленивый не описывал её по программированию в Linux.
Вот та элементарная классика, от которой я буду отталкиваться:

Код: Выделить всё

[olej@dell 6]$ cat +shell_a.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <stdbool.h>  // расширение стандарта C99
#include <string.h>
#include <signal.h>
#include <errno.h>
#include <sys/wait.h>

static void handler( int signo ) {
  printf( "\n> " );
  fflush( stdout );
}

#define MAXARGS 20    // max. число аргументов
#define ARGLEN  100   // max. длина аргумента

int main( int argc, char **argv, char **envp ) {
   signal( SIGINT, handler );
   signal( SIGQUIT, handler );
   char buf[ 256 ];
   while( true ) {
      char *arglist[ MAXARGS + 1 ], *b;
      printf( "> " );
      fflush( stdout );
      if( !fgets( buf, sizeof( buf ), stdin ) ) {
         printf( "\n" );
         break;
      }      
      *strrchr( buf, '\n' ) = '\0';
      if( !strlen( buf ) ) continue;
      if( strstr( buf, "exit" ) == buf || strstr( buf, "quit" ) == buf )
         break;
      int narg = 0;
      b = buf;
      while( strlen( b ) && strchr( b, ' ' ) && narg < MAXARGS - 1 ) {
         char arg[ ARGLEN + 1 ] = "";
         strncpy( arg, b, strchr( b, ' ' ) - b );
         arglist[ narg++ ] = strdup( arg ); 
         b = strchr( b, ' ' );
         b += strspn( b, " " ); 
      }
      if( strlen( b ) ) 
         arglist[ narg++ ] = strdup( b );
      arglist[ narg ] = NULL;
      pid_t pid = fork();
      int exitstatus;
      switch( pid ) {
         case -1:
            perror( "fork failed" );
            return errno;
         case 0:    
            execvp( arglist[ 0 ], arglist );
            perror( "execvp failed" );
            return errno;
         default: 
            while( wait( &exitstatus ) != pid );
      }
      for( int i = 0; i < MAXARGS + 1 && arglist[ i ] != NULL; i++ ) 
         free( arglist[ i ] );
   }
   return EXIT_SUCCESS; 
}
Он умеет только тупо выполнять одиночные команды:

Код: Выделить всё

[olej@dell 6]$ ./+shell_a 
> pwd
/home/olej/2016_WORK/own.WORK/PureCodeCpp/Angelina/6
> ls
bgr.c  dialog	 dialog.tst  f2      Makefile  pl.c	 +shell_a.c  +shell_b.c  +shell_c.c  +shell_d.c  time.report
d1     dialog.c  f1	     ls.txt  pipe.c    +shell_a  +shell_b    +shell_c	 +shell_d    shell.hist
> exit
Но, как видно, работает со списком путей $PATH ...
И отрабатываем диалог на такой, например, тестовой задаче:

Код: Выделить всё

#include <stdio.h>
#include <stdbool.h>  // расширение стандарта C99

int main( void ) {
   char buf[ 256 ];
   while( true ) {
      printf( ">> " );
      fflush( stdout );
      if( !fgets( buf, sizeof( buf ), stdin ) ) {
         printf( "\n" );
         break;
      }
      char *p = buf;
      while( *p != '\n' )
         ( *p++ )++;
      printf( "%s", buf );
   }
   return 0;
}

Код: Выделить всё

[olej@dell 6]$ ./+shell_a 
> ./dialog
>> 123
234
>> 345
456
>> qwerty
rxfsuz
>> 
> ls -l dialog.c
-rw-rw-r-- 1 olej olej 417 дек 20 12:37 dialog.c
> quit
Вложения
+shell_a.c
(1.77 КБ) 133 скачивания
dialog.c
(417 байт) 136 скачиваний

Аватара пользователя
Olej
Писатель
Сообщения: 21338
Зарегистрирован: 24 сен 2011, 14:22
Откуда: Харьков
Контактная информация:

Re: C: интерпретатор shell

Непрочитанное сообщение Olej » 21 дек 2016, 12:52

Olej писал(а): - конвейеры ... типа:

Код: Выделить всё

$ echo 12345 | cat | cat | cat | cat
Это сложная задача!
И сложность в том, что:
- нужно организовать N команд в конвейере;
- 1-й в конвейере открывает pipe только для SYSOUT
- последний в конвейере открывает pipe только для SYSIN
- все промежуточный открывают 2 pipe: для SYSOUT и для SYSIN
- причём SYSIN открывается к предыдущему pipe, а SYSOUT - следующему
- и в главной вызывающей программе должны быть закрыты все ненужные в данный момент pipe-ы, иначе на SYSIN не будет отрабатываться EOF

Поэтому для отработки сначала делаю простейший тест для такого вот конвейера:

Код: Выделить всё

[olej@dell 6]$ cat /etc/passwd | wc
     45     106    2510

Код, реализующий такой конвейер:

Код: Выделить всё

#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>

int main() {
   int fd[ 2 ];
   if( pipe( fd ) < 0 ) {
      printf( "Cannot create pipe\n" );
      return 1;
   }
   if( fork() == 0 ) {
      dup2( fd[ 1 ], 1 );
      close( fd[ 0 ] );
      close( fd[ 1 ] );
      execlp( "cat", "cat", "/etc/passwd", NULL );
      return 1;
   }
   if( fork() == 0 ) {
      dup2( fd[ 0 ], 0 );
      close( fd[ 0 ] );
      close( fd[ 1 ] );
      execlp( "wc", "wc", NULL );
      return 1;
   }
   close( fd[ 0 ] );
   close( fd[ 1 ] );
   return 0;
}
Играем:

Код: Выделить всё

[olej@dell 6]$ ./pipe
     45     106    2510
Вложения
pipe.c
(552 байт) 119 скачиваний

Аватара пользователя
Olej
Писатель
Сообщения: 21338
Зарегистрирован: 24 сен 2011, 14:22
Откуда: Харьков
Контактная информация:

Re: C: интерпретатор shell

Непрочитанное сообщение Olej » 21 дек 2016, 13:35

Olej писал(а): - конвейеры ... типа:

Код: Выделить всё

$ echo 12345 | cat | cat | cat | cat
Теперь можно расширять на сколь угодно длинный конвейер:

Код: Выделить всё

[olej@dell 6]$ cat +shell_b.c 
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <stdbool.h>  // расширение стандарта C99
#include <string.h>
#include <signal.h>
#include <errno.h>
#include <sys/wait.h>

#define MAXPIPE 10    // max. длина конвейера
#define MAXARGS 20    // max. число аргументов
#define ARGLEN  100   // max. длина аргумента

static void handler( int signo ) {
  printf( "\n> " );
  fflush( stdout );
}

typedef struct command {                   // отдельная команда конвейера
   char *arglist[ MAXARGS + 1 ];
} command_t;

void set_cmd( command_t *cmd, char *b ) {  // разбор команды 
   int narg = 0;
   while( strlen( b ) && strchr( b, ' ' ) && narg < MAXARGS - 1 ) {
      char arg[ ARGLEN + 1 ] = "";
      strncpy( arg, b, strchr( b, ' ' ) - b );
      cmd->arglist[ narg++ ] = strdup( arg ); 
      b = strchr( b, ' ' );
      b += strspn( b, " " ); 
   }
   if( strlen( b ) ) 
      cmd->arglist[ narg++ ] = strdup( b );
   cmd->arglist[ narg ] = NULL;
}

void free_cmd( command_t *cmd ) {
   for( int i = 0; i < MAXARGS + 1 && cmd->arglist[ i ] != NULL; i++ ) 
      free( cmd->arglist[ i ] );
}

void print_cmd( command_t *cmd ) {
   for( int i = 0; i < MAXARGS + 1 && cmd->arglist[ i ] != NULL; i++ ) 
      printf( "<%s> ", cmd->arglist[ i ] );
   printf( "\n" );          
}

int main( int argc, char **argv, char **envp ) {
   signal( SIGINT, handler );
   signal( SIGQUIT, handler );
   command_t pipeline[ MAXPIPE ];
   while( true ) {
      char buf[ 1024 ];
      printf( "> " );
      fflush( stdout );
      if( !fgets( buf, sizeof( buf ), stdin ) ) {
         printf( "\n" );
         break;
      }      
      *strrchr( buf, '\n' ) = '\0';
      if( !strlen( buf ) ) continue;
      if( strstr( buf, "exit" ) == buf || strstr( buf, "quit" ) == buf )
         break;
      int npipe = 0;
      char *b = buf + strspn( buf, " " );
      while( true ) {                      // разделение конвейера на команды
         const char *delim = " | ";
         char cmd[ 256 ] = "";
         if( strstr( b, " | " ) )
            strncpy( cmd, b, strstr( b, " | " ) - b );
         else
            strcpy( cmd, b );
         set_cmd( pipeline + npipe, cmd );
         npipe++;
         if( strstr( b, " | ") ) {
            b = strstr( b, " | ") + strlen( delim );
            b += strspn( b, " " );
         } 
         else break;
      }   
      {  int fd[ npipe ][ 2 ];             // VLA !
         for( int i = 0; i < npipe; i++ ) {
            if( pipe( fd[ i ] ) < 0 ) {
               perror( "pipe failed" );
               return 1;
            }
            pid_t pid = fork();            // команда конвейера
            if( pid < 0 ) { 
               perror( "fork failed" );
               return 1;
            }
            if( 0 == pid ) {
               if( i < npipe - 1 ) {       // переадресация SYSOUT 
                  dup2( fd[ i ][ 1 ], 1 );
                  close( fd[ i ][ 0 ] );
                  close( fd[ i ][ 1 ] );               
               }
               if( i > 0 ) {               // переадресация SYSIN    
                  dup2( fd[ i - 1 ][ 0 ], 0 );
                  close( fd[ i - 1 ][ 0 ] );
                  close( fd[ i - 1 ][ 1 ] );               
               }
               execvp( pipeline[ i ].arglist[ 0 ], pipeline[ i ].arglist );
               perror( "execvp failed" );
               return 1;
            }
            if( i > 0 ) {                  // родительский shell 
               close( fd[ i - 1 ][ 0 ] );
               close( fd[ i - 1 ][ 1 ] );
            }
         }  // for по конвейеру  
      } // block {...}
      for( int i = 0; i < npipe; i++ )     // осовободить текст конвейера
         free_cmd( pipeline + i );
      for( int i = 0; i < npipe; i++ ) {   // ожидать выполнения
         int exitstatus;
         wait( &exitstatus );
      }
   }
   return EXIT_SUCCESS; 
}
Пробуем:

Код: Выделить всё

[olej@dell 6]$ ./+shell_b
> echo 5 | cat | cat | cat | cat
5
> ./dialog | cat
>> 123
234
>> 456
567
>> 
> quit
Вложения
+shell_b.c
(4.03 КБ) 128 скачиваний

Аватара пользователя
Olej
Писатель
Сообщения: 21338
Зарегистрирован: 24 сен 2011, 14:22
Откуда: Харьков
Контактная информация:

Re: C: интерпретатор shell

Непрочитанное сообщение Olej » 21 дек 2016, 13:40

Olej писал(а): Теперь можно расширять на сколь угодно длинный конвейер:
Там есть один трюк, допускаемый только стандартом C99:

Код: Выделить всё

      {  int fd[ npipe ][ 2 ];             // VLA !
         for( int i = 0; i < npipe; i++ ) {
            if( pipe( fd[ i ] ) < 0 ) {
               perror( "pipe failed" );
               return 1;
            }
...
      }
Это Variable Length Array, и для его создания понадобился отдельный охватывающий блок {...}
(это сделано чтобы не возиться с calloc()/free() ... можно сделать alloca())

Аватара пользователя
Olej
Писатель
Сообщения: 21338
Зарегистрирован: 24 сен 2011, 14:22
Откуда: Харьков
Контактная информация:

Re: C: интерпретатор shell

Непрочитанное сообщение Olej » 21 дек 2016, 13:47

Olej писал(а): - перенаправление ввода-вывода ... типа:

Код: Выделить всё

$ cat <f1 >f2
$ cat <f1 >>f2
Добавляем:

Код: Выделить всё

[olej@dell 6]$ cat +shell_c.c 
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <stdbool.h>  // расширение стандарта C99
#include <string.h>
#include <signal.h>
#include <errno.h>
#include <sys/wait.h>
#include <fcntl.h>


#define MAXPIPE 10    // max. длина конвейера
#define MAXARGS 20    // max. число аргументов
#define ARGLEN  100   // max. длина аргумента

static void handler( int signo ) {
  printf( "\n> " );
  fflush( stdout );
}

char *delim[] = { "<", ">>", ">" };
const int ndlm = sizeof( delim ) / sizeof( delim[ 0 ] );
typedef struct command {                   // отдельная команда конвейера
   char *arglist[ MAXARGS + 1 ];
   char *finout[ sizeof( delim ) / sizeof( delim[ 0 ] ) ];  
} command_t;

void set_cmd( command_t *cmd, char *b ) {  // разбор команды 
   for( int i = 0; i < ndlm; i++ ) cmd->finout[ i ] = NULL;
   int narg = 0;
   while( true ) {
      char arg[ ARGLEN + 1 ] = "";
      if( strchr( b, ' ' ) )
         strncpy( arg, b, strchr( b, ' ' ) - b );
      else
         strcpy( arg, b );
      bool bdir = false; 
      for( int i = 0; i < ndlm; i++ ) {
         char *p = strstr( arg, delim[ i ] ); 
         if( p ) {
            bdir = true;
            p += strlen( delim[ i ] );
            char file[ 256 ] = "";
            cmd->finout[ i ] = strdup( strchr( p, ' ' ) ? 
                                  strncpy( file, p, strchr( p, ' ' ) - p ) :
                                  strcpy( file, p ) ); 
            break;
         }
      }
      if( !bdir ) 
         cmd->arglist[ narg++ ] = strdup( arg ); 
      if( strchr( b, ' ' ) ) {
         b = strchr( b, ' ' );
         b += strspn( b, " " );
      }
      else break; 
   }
   cmd->arglist[ narg ] = NULL;
}

void free_cmd( command_t *cmd ) {
   for( int i = 0; i < MAXARGS + 1 && cmd->arglist[ i ] != NULL; i++ ) {
      free( cmd->arglist[ i ] );
      cmd->arglist[ i ] = NULL;
   }
   for( int i = 0; i < ndlm; i++ )
      if( cmd->finout[ i ] )
         free( cmd->finout[ i ] );
}

void print_cmd( command_t *cmd ) {
   printf( "args: " );          
   for( int i = 0; i < MAXARGS + 1 && cmd->arglist[ i ] != NULL; i++ ) 
      printf( "[%s] ", cmd->arglist[ i ] );
   bool bdir = false;
   for( int i = 0; i < ndlm; i++ )
      if( cmd->finout[ i ] ) {
         bdir = true; 
         break;
      }
   if( bdir) { 
      printf( " | " );     
      for( int i = 0; i < ndlm; i++ )
         if( cmd->finout[ i ] )
            printf( "%s%s ", delim[ i ], cmd->finout[ i ] ); 
      printf( "|" );     
   }
   printf( "\n" );          
}

int main( int argc, char **argv, char **envp ) {
   int debug_level = 0, c;
   while( -1 != ( c = getopt( argc, argv, "v" ) ) )
      switch( c ) {
         case 'v': debug_level++; break;
         default : return 1;
      }
   signal( SIGINT, handler );
   signal( SIGQUIT, handler );
   command_t pipeline[ MAXPIPE ];
   while( true ) {
      char buf[ 1024 ];
      printf( "> " );
      fflush( stdout );
      if( !fgets( buf, sizeof( buf ), stdin ) ) {
         printf( "\n" );
         break;
      }      
      *strrchr( buf, '\n' ) = '\0';
      if( !strlen( buf ) ) continue;
      if( strstr( buf, "exit" ) == buf || strstr( buf, "quit" ) == buf )
         break;
      int npipe = 0;
      char *b = buf + strspn( buf, " " );
      while( true ) {                      // разделение конвейера на команды
         const char *delim = " | ";
         char cmd[ 256 ] = "";
         if( strstr( b, " | " ) )
            strncpy( cmd, b, strstr( b, " | " ) - b );
         else
            strcpy( cmd, b );
         set_cmd( pipeline + npipe, cmd );
         npipe++;
         if( strstr( b, " | ") ) {
            b = strstr( b, " | ") + strlen( delim );
            b += strspn( b, " " );
         } 
         else break;
      }   
      if( debug_level )
         for( int i = 0; i < npipe; i++ ) 
            print_cmd( pipeline + i );
      {  int fd[ npipe ][ 2 ];             // VLA !
         for( int i = 0; i < npipe; i++ ) {
            if( pipe( fd[ i ] ) < 0 ) {
               perror( "pipe failed" );
               return 1;
            }
            pid_t pid = fork();            // выполняется команда конвейера
            if( pid < 0 ) { 
               perror( "fork failed" );
               return 1;
            }
            if( 0 == pid ) {               // старт команды (дочерний процесс)
               if( pipeline[ i ].finout[ 0 ] != NULL ) {  // <
                  close( 0 );
                  int fdi = open( pipeline[ i ].finout[ 0 ], O_RDONLY );
                  if( fdi < 0 ) {
                     perror( "input channel" );
                     return 1;
                  }
               }
               if( pipeline[ i ].finout[ 1 ] != NULL ) {  // >>
                  close( 1 );
                  int fdo = open( pipeline[ i ].finout[ 1 ], O_WRONLY | O_CREAT | O_APPEND,
                              S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH );
                  if( fdo < 0 ) {
                     perror( "output channel" );
                     return 1;
                  }
               }
               if( pipeline[ i ].finout[ 2 ] != NULL ) {  // >
                  close( 1 );
                  int fdo = open( pipeline[ i ].finout[ 2 ], O_WRONLY | O_CREAT | O_TRUNC,
                              S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH );
                  if( fdo < 0 ) {
                     perror( "output channel" );
                     return 1;
                  }
               }
               if( i < npipe - 1 ) {       // переадресация SYSOUT в канал
                  dup2( fd[ i ][ 1 ], 1 );
                  close( fd[ i ][ 0 ] );
                  close( fd[ i ][ 1 ] );               
               }
               if( i > 0 ) {               // переадресация SYSIN в канал
                  dup2( fd[ i - 1 ][ 0 ], 0 );
                  close( fd[ i - 1 ][ 0 ] );
                  close( fd[ i - 1 ][ 1 ] );   
               }
               execvp( pipeline[ i ].arglist[ 0 ], pipeline[ i ].arglist );
               perror( "execvp failed" );
               return 1;
            }
            if( i > 0 ) {                  // родительский shell 
               close( fd[ i - 1 ][ 0 ] );
               close( fd[ i - 1 ][ 1 ] );
            }
         }  // for по конвейеру  
      } // block {...}
      for( int i = 0; i < npipe; i++ )     // осовободить текст конвейера
         free_cmd( pipeline + i );
      for( int i = 0; i < npipe; i++ ) {   // ожидать выполнения
         int exitstatus;
         wait( &exitstatus );
      }
   }
   return EXIT_SUCCESS; 
}
Проверяем:

Код: Выделить всё

[olej@dell 6]$ ./+shell_с
> ./dialog >d2
1234
2345
0987
> ls d2
d2
> ./dialog <d2 >d3
> ls -l d2 d3
-rw-r--r-- 1 olej olej 28 дек 21 12:12 d2
-rw-r--r-- 1 olej olej 44 дек 21 12:12 d3
> ./dialog <d2 >>d3
> ls -l d2 d3
-rw-r--r-- 1 olej olej 28 дек 21 12:12 d2
-rw-r--r-- 1 olej olej 88 дек 21 12:13 d3
> cat d3
>> ??!3456
>> ??!4567
>> ??!2;:9
>> ??!
>>
>> ??!3456
>> ??!4567
>> ??!2;:9
>> ??!
>>
> exit
Вложения
+shell_c.c
(6.74 КБ) 122 скачивания

Аватара пользователя
Olej
Писатель
Сообщения: 21338
Зарегистрирован: 24 сен 2011, 14:22
Откуда: Харьков
Контактная информация:

Re: C: интерпретатор shell

Непрочитанное сообщение Olej » 21 дек 2016, 13:54

Olej писал(а): - запуск в фоновом режиме ... типа:

Код: Выделить всё

$ prog &
[14567]
Остаётся добавить...
В принципе, самое первое, черновое решение:

Код: Выделить всё

[olej@dell 6]$ cat +shell_d.c 
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <stdbool.h>  // расширение стандарта C99
#include <string.h>
#include <signal.h>
#include <errno.h>
#include <sys/wait.h>
#include <fcntl.h>


#define MAXPIPE 10    // max. длина конвейера
#define MAXARGS 20    // max. число аргументов
#define ARGLEN  100   // max. длина аргумента

static void handler( int signo ) {
  printf( "\n> " );
  fflush( stdout );
}

char *delim[] = { "<", ">>", ">" };
const int ndlm = sizeof( delim ) / sizeof( delim[ 0 ] );
typedef struct command {                   // отдельная команда конвейера
   char *arglist[ MAXARGS + 1 ];
   char *finout[ sizeof( delim ) / sizeof( delim[ 0 ] ) ];
   bool bground;
} command_t;

void set_cmd( command_t *cmd, char *b ) {  // разбор команды 
   for( int i = 0; i < ndlm; i++ ) cmd->finout[ i ] = NULL;
   cmd->bground = false;
   int narg = 0;
   while( true ) {
      char arg[ ARGLEN + 1 ] = "";
      if( strchr( b, ' ' ) )
         strncpy( arg, b, strchr( b, ' ' ) - b );
      else
         strcpy( arg, b );
      bool bdir = false; 
      for( int i = 0; i < ndlm; i++ ) {    // переадресация
         char *p = strstr( arg, delim[ i ] ); 
         if( p ) {
            bdir = true;
            p += strlen( delim[ i ] );
            char file[ 256 ] = "";
            cmd->finout[ i ] = strdup( strchr( p, ' ' ) ? 
                                  strncpy( file, p, strchr( p, ' ' ) - p ) :
                                  strcpy( file, p ) ); 
            break;
         }
      }
      if( 0 == strcmp( arg, "&" ) ) {      // фоновый режим
         cmd->bground = true;
         bdir = true;
      }
      if( !bdir ) 
         cmd->arglist[ narg++ ] = strdup( arg ); 
      if( strchr( b, ' ' ) ) {
         b = strchr( b, ' ' );
         b += strspn( b, " " );
      }
      else break; 
   }
   cmd->arglist[ narg ] = NULL;
}

void free_cmd( command_t *cmd ) {
   for( int i = 0; i < MAXARGS + 1 && cmd->arglist[ i ] != NULL; i++ ) {
      free( cmd->arglist[ i ] );
      cmd->arglist[ i ] = NULL;
   }
   for( int i = 0; i < ndlm; i++ )
      if( cmd->finout[ i ] )
         free( cmd->finout[ i ] );
}

void print_cmd( command_t *cmd ) {
   printf( "args: " );          
   for( int i = 0; i < MAXARGS + 1 && cmd->arglist[ i ] != NULL; i++ ) 
      printf( "[%s] ", cmd->arglist[ i ] );
   bool bdir = false;
   for( int i = 0; i < ndlm; i++ )
      if( cmd->finout[ i ] ) {
         bdir = true; 
         break;
      }
   if( bdir) { 
      printf( " | " );     
      for( int i = 0; i < ndlm; i++ )
         if( cmd->finout[ i ] )
            printf( "%s%s ", delim[ i ], cmd->finout[ i ] ); 
      printf( "|" );     
   }
   if( cmd->bground )
      printf( " &" ); 
   printf( "\n" );          
}

int main( int argc, char **argv, char **envp ) {
   int debug_level = 0, c;
   while( -1 != ( c = getopt( argc, argv, "v" ) ) )
      switch( c ) {
         case 'v': debug_level++; break;
         default : return 1;
      }
   signal( SIGINT, handler );
   signal( SIGQUIT, handler );
   command_t pipeline[ MAXPIPE ];
   while( true ) {
      char buf[ 1024 ];
      printf( "> " );
      fflush( stdout );
      if( !fgets( buf, sizeof( buf ), stdin ) ) {
         printf( "\n" );
         break;
      }      
      *strrchr( buf, '\n' ) = '\0';
      if( !strlen( buf ) ) continue;
      if( strstr( buf, "exit" ) == buf || strstr( buf, "quit" ) == buf )
         break;
      int npipe = 0;
      char *b = buf + strspn( buf, " " );
      while( true ) {                      // разделение конвейера на команды
         const char *delim = " | ";
         char cmd[ 256 ] = "";
         if( strstr( b, " | " ) )
            strncpy( cmd, b, strstr( b, " | " ) - b );
         else
            strcpy( cmd, b );
         set_cmd( pipeline + npipe, cmd );
         npipe++;
         if( strstr( b, " | ") ) {
            b = strstr( b, " | ") + strlen( delim );
            b += strspn( b, " " );
         } 
         else break;
      }   
      if( debug_level )
         for( int i = 0; i < npipe; i++ ) 
            print_cmd( pipeline + i );
      int nwait = npipe;
      {  int fd[ npipe ][ 2 ];             // VLA !
         for( int i = 0; i < npipe; i++ ) {
            if( pipe( fd[ i ] ) < 0 ) {
               perror( "pipe failed" );
               return 1;
            }
            pid_t pid = fork();            // выполняется команда конвейера
            if( pid < 0 ) { 
               perror( "fork failed" );
               return 1;
            }
            if( 0 == pid ) {               // старт команды (дочерний процесс)
               if( pipeline[ i ].finout[ 0 ] != NULL ) {  // <
                  close( 0 );
                  int fdi = open( pipeline[ i ].finout[ 0 ], O_RDONLY );
                  if( fdi < 0 ) {
                     perror( "input channel" );
                     return 1;
                  }
               }
               if( pipeline[ i ].finout[ 1 ] != NULL ) {  // >>
                  close( 1 );
                  int fdo = open( pipeline[ i ].finout[ 1 ], O_WRONLY | O_CREAT | O_APPEND,
                              S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH );
                  if( fdo < 0 ) {
                     perror( "output channel" );
                     return 1;
                  }
               }
               if( pipeline[ i ].finout[ 2 ] != NULL ) {  // >
                  close( 1 );
                  int fdo = open( pipeline[ i ].finout[ 2 ], O_WRONLY | O_CREAT | O_TRUNC,
                              S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH );
                  if( fdo < 0 ) {
                     perror( "output channel" );
                     return 1;
                  }
               }
               if( i < npipe - 1 ) {       // переадресация SYSOUT в канал
                  dup2( fd[ i ][ 1 ], 1 );
                  close( fd[ i ][ 0 ] );
                  close( fd[ i ][ 1 ] );               
               }
               if( i > 0 ) {               // переадресация SYSIN в канал
                  dup2( fd[ i - 1 ][ 0 ], 0 );
                  close( fd[ i - 1 ][ 0 ] );
                  close( fd[ i - 1 ][ 1 ] );   
               }
               if( pipeline[ i ].bground ) { 
                  close( 0 ); close( 1 ); close( 2 );
                  int fd0 = open( "/dev/null", O_RDWR ),
                      fd1 = dup( 0 ),
                      fd2 = dup( 0 );
                  if( fd0 != 0 || fd1 != 1 || fd2 != 2 )
                     printf( "demonize error!\n" );                         
               }
               execvp( pipeline[ i ].arglist[ 0 ], pipeline[ i ].arglist );
               perror( "execvp failed" );
               return 1;
            }
            if( pipeline[ i ].bground ) { 
               printf( "[%u]\n", pid );
               nwait--; 
            }            
            if( i > 0 ) {                  // родительский shell 
               close( fd[ i - 1 ][ 0 ] );
               close( fd[ i - 1 ][ 1 ] );
            }
         }  // for по конвейеру  
      } // block {...}
      for( int i = 0; i < npipe; i++ )     // осовободить текст конвейера
         free_cmd( pipeline + i );
      for( int i = 0; i < nwait; i++ ) {   // ожидать выполнения
         int exitstatus;
         wait( &exitstatus );
      }
   }
   return EXIT_SUCCESS; 
}
Проверяем (ним же запускаем - ним же и смотрим):

Код: Выделить всё

[olej@dell 6]$ ./+shell_d
> ./bgr &
[17731]
> ps -A | grep bgr
17731 pts/6    00:00:00 bgr
> ps -A | grep bgr
17731 pts/6    00:00:00 bgr
> ps -A | grep bgr
17731 pts/6    00:00:00 bgr
> ps -A | grep bgr
> exit
Запускаем такую бэкграундную тест программу:

Код: Выделить всё

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

int main( int argc, char *argv[] ) {
   int delay = 1 == argc ? 30 : atoi( argv[ 1 ] );
   sleep( delay );
   return 0;
}
Для сравнения - как тот же бэкграунд выполняет стандартный bash:

Код: Выделить всё

[olej@dell 6]$ ./bgr &
[1] 17959
Вложения
+shell_d.c
(7.51 КБ) 112 скачиваний
bgr.c
(183 байт) 127 скачиваний

Аватара пользователя
Olej
Писатель
Сообщения: 21338
Зарегистрирован: 24 сен 2011, 14:22
Откуда: Харьков
Контактная информация:

Re: C: интерпретатор shell

Непрочитанное сообщение Olej » 21 дек 2016, 14:07

Olej писал(а): Остаётся добавить...
В принципе, самое первое, черновое решение:
Для такого решения "черновость" состоит в том, что после выполнения бэкграунда:

Код: Выделить всё

[olej@dell 6]$ ps -A | grep bgr
  499 pts/6    00:00:00 bgr
После завершения на некоторое время образуется зомби:

Код: Выделить всё

[olej@dell 6]$ ps -A | grep bgr
  499 pts/6    00:00:00 bgr <defunct>
По классике UNIX зомби существуют вечно...
Но это не так в современном Linux - после некоторого непродолжительного времени зомби уничтожаются.
(через какое время?)

Аватара пользователя
Olej
Писатель
Сообщения: 21338
Зарегистрирован: 24 сен 2011, 14:22
Откуда: Харьков
Контактная информация:

Re: C: интерпретатор shell

Непрочитанное сообщение Olej » 21 дек 2016, 15:00

Olej писал(а): По классике UNIX зомби существуют вечно...
Но это не так в современном Linux - после некоторого непродолжительного времени зомби уничтожаются.
(через какое время?)
Запускаю:

Код: Выделить всё

[olej@dell 6]$ ./+shell_d 
> ./bgr 10 &
[22376]
Параллельно смотрим скрипт ./ps периодически:

Код: Выделить всё

date
ps -A | grep bgr

Код: Выделить всё

[olej@dell 6]$ ./ps
Ср дек 21 13:30:55 EET 2016
22376 pts/1    00:00:00 bgr
...
[olej@dell 6]$ ./ps
Ср дек 21 13:44:58 EET 2016
22376 pts/1    00:00:00 bgr <defunct>
15 мин. ... +shell_d - работает, мёртвая запись о зомби bgr - висит...

Код: Выделить всё

[olej@dell 6]$ ps -Ao pid,ppid | grep bgr
[olej@dell 6]$ ps -Ao pid,ppid,comm | grep bgr
22376 22359 bgr <defunct>
[olej@dell 6]$ ps -A | grep 22359
22359 pts/1    00:00:00 +shell_d
Завершаем свой shell:

Код: Выделить всё

[olej@dell 6]$ ./+shell_d 
> ./bgr 10 &
[22376]
> exit
И наблюдаем:

Код: Выделить всё

[olej@dell 6]$ ./ps
Ср дек 21 13:48:51 EET 2016
[olej@dell 6]$ ps -A | grep bgr
[olej@dell 6]$ 
Нет никакого зомби!
В принципе, он, потеряв родителя, должен перевеситься к PID=1 ... к кому?

Код: Выделить всё

[olej@dell 6]$ ps -A | head -n10
  PID TTY          TIME CMD
    1 ?        00:00:55 systemd
    2 ?        00:00:00 kthreadd
    3 ?        00:00:01 ksoftirqd/0
    5 ?        00:00:00 kworker/0:0H
    7 ?        00:14:27 rcu_sched
    8 ?        00:00:00 rcu_bh
    9 ?        00:05:07 rcuos/0
   10 ?        00:00:00 rcuob/0
   11 ?        00:00:00 migration/0

Аватара пользователя
Olej
Писатель
Сообщения: 21338
Зарегистрирован: 24 сен 2011, 14:22
Откуда: Харьков
Контактная информация:

Re: C: интерпретатор shell

Непрочитанное сообщение Olej » 21 дек 2016, 15:10

Olej писал(а): Нет никакого зомби!
В принципе, он, потеряв родителя, должен перевеситься к PID=1 ... к кому?

Код: Выделить всё

[olej@dell 6]$ ps -A | head -n10
  PID TTY          TIME CMD
    1 ?        00:00:55 systemd
    2 ?        00:00:00 kthreadd
    3 ?        00:00:01 ksoftirqd/0
    5 ?        00:00:00 kworker/0:0H
    7 ?        00:14:27 rcu_sched
    8 ?        00:00:00 rcu_bh
    9 ?        00:05:07 rcuos/0
   10 ?        00:00:00 rcuob/0
   11 ?        00:00:00 migration/0
Как убить Systemd с помощью одной команды
Мы не говорим о сложности всего Systemd, нас интересует только PID 1. Единственная задача, которую он должен выполнять - это инициализировать систему инициализации и убивать зомби процессы. Все остальное же должно быть построено по модульному принципу, чтобы сбой в одном из модулей не рушил всю систему.
Так что все сказки, пересказываемые в тысячах экземпляров Интернет о зомби, пересказывают состояние дел ... лет 30 назад!
А поскольку все дистрибутивы Linux перешли, переходят, или обязаны будут перейти на систему инициализации systemd, то состояние с зомби теперь радикально меняется!

Ответить

Вернуться в «Программирование»

Кто сейчас на конференции

Сейчас этот форум просматривают: нет зарегистрированных пользователей и 6 гостей