- Minishell with Dimitris
While minishell aims to replicate essential features of bash, its behavior is intentionally restricted to meet the scope of the subject.
Only specific functionalities are required and implemented, such as basic quoting, redirections, pipes, environment variable expansion, and a minimal set of built-in commands.
Advanced features like job control, command substitution, process groups, or extended globbing are out of scope. Additionally, only one global variable (for signal handling) is allowed, limiting inter-process state tracking. Our implementation also uses /proc/self/stat to introspect process-related metrics and /dev/urandom to generate randomness where needed; however, portability is constrained to Unix-like systems with a working /proc and /dev filesystem.
These limitations reflect both project constraints and OS-specific dependencies, and are not intended to fully emulate a production-ready shell like bash.
-
Clone the repository:
git clone [email protected]:ThiwankaS/Minishell.git && cd Minishell
-
Build the project:
make
This will compile the
minishellexecutable using the providedMakefile. Make sure your system has a C compiler (cc) andreadlinedevelopment libraries installed.
To start the shell, simply run:
./minishellYou will see a custom prompt indicating that the shell is waiting for input. You can now execute built-in commands (cd, echo, pwd, etc.) and external programs (like /bin/ls, cat, etc.).
Ctrl-C: Interrupts the current command and shows a new prompt.Ctrl-D: Exits the shell.Ctrl-\: Does nothing (ignored, like inbash).
- The shell supports basic features such as pipelines (
|), redirections (>,<,>>,<<), and environment variable expansion ($VAR,$?). - Only the features specified in the subject are implemented; anything beyond that may not work or is deliberately excluded.
char *readline (char *prompt);So, exapmle of using the function
char *line = readline("Enter a line: ");readline returns the text of the line read. A blank line returns the empty string. If EOF is encountered while reading a line, and the line is empty, NULL is returned. If an EOF is read with a non-empty line, it is treated as a newline.
void add_history(const char *line);The add_history function is also part of the GNU Readline library and is used to add a line of input to the history list.
void rl_clear_history(void);The rl_clear_history function in C is part of the GNU Readline library. It's used to clear the entire input history that Readline maintains — for example, the lines you've entered during an interactive session
int rl_on_new_line(void);Tells Readline that the cursor is on a new line, so it's ready to redraw the prompt and the current input line correctly. Returns 0 on success, -1 if the internal line state is invalid (e.g. rl_line_buffer is NULL).
void rl_replace_line(const char *text, int clear_undo);The rl_replace_line function from the GNU Readline library is used to replace the current input line with a new string — without affecting the command history.
text: The new string to use as the input lineclear_undo: If non-zero, clears the undo list
It changes what the user sees as the current line in the terminal.
void rl_redisplay(void);The rl_redisplay function is part of the GNU Readline library, and it’s used to redraw the prompt and current input line on the screen. Forces Readline to refresh the current input line and prompt display.
After making changes to the input buffer (e.g. with rl_replace_line), or when terminal output messes up the current input display.
char *getcwd(char *buf, size_t size);The getcwd function in C is used to get the current working directory of the calling process.
- buf: A pointer to a buffer where the path will be stored.
- size: The size (in bytes) of that buffer.
Returns: A pointer to
bufon success, orNULLon failure (e.g. if the buffer is too small). If you wantgetcwdto allocate memory for you (POSIX extension), passNULLforbufand0forsize.
int chdir(const char *path);The chdir() function in C is used to change the current working directory of the running process.
path: A string representing the new directory path. Returns0on success-1on failure (and sets errno).
int stat(const char *pathname, struct stat *statbuf);The stat() function in C is used to get information about a file or directory — like its size, permissions, timestamps, and more.
pathname: Path to the file or directory.statbuf: Pointer to a struct stat where the info will be stored. Returns,0on success,-1on failure, and sets errno.
struct stat– Key Fields
st_modeFile type and permissionsst_sizeFile size in bytesst_uidUser ID of ownerst_gidGroup ID of ownerst_mtimeTime of last modificationst_ctimeTime of last status changest_atimeTime of last access
int lstat(const char *pathname, struct stat *statbuf);The lstat function in C is very similar to stat(), but with one key difference: lstat does not follow symbolic links, while stat does. In writing a tool like ls -l or find, and want to list symlinks without resolving them, use lstat.
int fstat(int fd, struct stat *statbuf);The fstat function in C is used to get file information from an open file descriptor, instead of a file path.
fd: A file descriptor (from open(), fileno(), pipe(), etc.).statbuf: A pointer to a struct stat that will be filled with file info. Returns0on success.-1on failure (check errno).
int unlink(const char *pathname);The unlink function in C is used to delete (remove) a file from the filesystem.unlink only removes the name from the directory. The file's contents are deleted only when no process has it open and there are no other links to it. Works like rm command in the shell.
int execve(const char *pathname, char *const argv[], char *const envp[]);The execve function in C is a low-level system call that replaces the current process image with a new program — meaning it starts another program and never returns (unless there’s an error).
pathnameFull path to the executable file (e.g., /bin/ls)argv[]Null-terminated array of argument strings. argv[0] should be the program name.envp[]Null-terminated array of environment variables (like PATH, HOME, etc.)
int dup(int oldfd);The dup() function in C is used to duplicate a file descriptor. It creates a copy of an existing file descriptor that refers to the same open file or resource.
oldfd: An existing (open) file descriptor (e.g., for a file, pipe, or socket). Returns, A new file descriptor with the lowest unused number on success or-1on failure (and sets errno).
int dup2(int oldfd, int newfd);The dup2() function in C is used to duplicate a file descriptor into a specific file descriptor number, often for I/O redirection (like redirecting stdout to a file).
oldfdThe original file descriptor you want to duplicatenewfdThe desired file descriptor number to copy into (e.g., STDOUT_FILENO) If newfd is already open, it is closed automatically before being reused.dup2()is perfect for redirecting standard input/output/error (STDIN_FILENO, STDOUT_FILENO, STDERR_FILENO). Ifoldfd == newfd,dup2()does nothing and returns newfd. If newfd was already open, it will be safely closed first (to avoid leaks). Returns the new file descriptor (newfd) on success, or-1on error.
int pipe(int pipefd[2]);The pipe() function in C is used to create a unidirectional communication channel between processes — usually between a parent and child process. It provides inter-process communication (IPC) via file descriptors: one for reading, one for writing. pipefd: An array of two integers:
pipefd[0]: Read endpipefd[1]: Write end Returns:0on success-1on failure (and sets errno).
DIR *opendir(const char *name);name: Path to the directory (e.g., "." for the current directory). Returns, A pointer to aDIR structure(directory stream) on success.NULLon failure (check errno for details).
struct dirent- Key Fields
d_nameName of the file (string)d_typeType of file (if available, e.g. regular file, dir)
struct dirent *readdir(DIR *dirp);The readdir function in C is used to read entries from an open directory stream, one at a time. It's commonly used after opendir to list files and subdirectories.
dirp: A pointer to a DIR object returned byopendir. Returns, A pointer to astruct direntrepresenting the next directory entry.NULLwhen there are no more entries (or on error).
int closedir(DIR *dirp);The closedir function in C is used to close a directory stream that was opened with opendir.
dirp: A pointer to a DIR object (returned byopendir). Returns0on success-1on failure (and sets errno).
char *strerror(int errnum);The strerror function in C is used to convert an error number (errno) into a human-readable error message — like "Permission denied" or "No such file or directory".
errnum: An error number, usually obtained from the global variable errno (set by functions like open(), read(), execve(), etc.).
strerror returns a pointer to a static string, so don’t modify or free it.
void perror(const char *s);The perror function in C is used to print a descriptive error message to stderr, based on the current value of the global variable errno.
s: A custom message you want to prefix the error with (like the function name or context).
int isatty(int fd);The isatty function in C is used to check if a file descriptor refers to a terminal (TTY).
fd: A file descriptor (e.g. STDIN_FILENO, STDOUT_FILENO, or any open file descriptor). Returns,1(true) if the file descriptor is connected to a terminal device.0(false) if not (and sets errno).
char *ttyname(int fd);The ttyname function in C is used to get the name of the terminal (TTY) device associated with a given file descriptor.
fd: A file descriptor (e.g.,STDIN_FILENO,STDOUT_FILENO, etc.). Returns, A pointer to a string containing the terminal device name (like /dev/tty1 or /dev/pts/0) on success.NULLon failure (e.g. if fd is not a terminal), and sets errno.
int ttyslot(void);The ttyslot function in C is used to get the slot number of the terminal in the system's terminal database (usually /etc/ttys or /etc/utmp).
It helps identify which terminal device a user is connected to.
int ioctl(int fd, unsigned long request, ...);The ioctl() function in C is a powerful, low-level system call used to perform device-specific input/output operations on file descriptors — often terminals, sockets, or special devices.
fdFile descriptor (e.g., terminal, socket, file)requestDevice-specific control code (macro like TIOCGWINSZ, FIONREAD, etc.)...Optional third argument — a pointer to data to send/receive. Returns0on success,-1on failure (sets errno).
char *getenv(const char *name);The getenv() function in C retrieves the value of an environment variable specified by name. It returns a pointer to the value string if the variable exists, or NULL if it is not found. The returned string must not be modified. This function is commonly used to access variables like PATH, HOME, or USER from the environment.
int tcsetattr(int fd, int optional_actions, const struct termios *termios_p);The tcsetattr function sets terminal attributes for the terminal referred to by the file descriptor fd. The optional_actions argument determines when the changes take effect (TCSANOW, TCSADRAIN, or TCSAFLUSH), and termios_p is a pointer to a termios structure containing the new terminal settings. It returns 0 on success and -1 on failure, setting errno accordingly.
int tcgetattr(int fd, struct termios *termios_p);The tcgetattr function retrieves the current terminal attributes for the terminal referred to by the file descriptor fd and stores them in the structure pointed to by termios_p. It is commonly used before modifying terminal settings with tcsetattr. Returns 0 on success and -1 on failure, setting errno accordingly.
int tgetent(char *bp, const char *name);The tgetent function loads the terminal entry for the terminal type specified by name from the termcap database and stores it in the buffer bp. If bp is NULL, a default internal buffer is used. Returns 1 if the terminal type was found, 0 if it was not found, and -1 if the database could not be accessed.
int tgetflag(const char *id);The tgetflag function retrieves the boolean value of a terminal capability specified by the two-character identifier id from the termcap database. It returns 1 if the capability is present, 0 if not, and -1 if an error occurs or the capability is unknown.
int tgetnum(const char *id);The tgetnum function retrieves the numeric value of a terminal capability specified by the two-character identifier id from the termcap database. It returns the value as an int if the capability exists, -1 if the capability is not available or an error occurs.
char *tgetstr(const char *id, char **area);The tgetstr() function retrieves the string value of a terminal capability specified by the two-character identifier id from the termcap database. The resulting string is stored in the buffer pointed to by *area, which is updated to point past the end of the stored string. Returns a pointer to the string if found, or NULL if the capability does not exist or an error occurs.
char *tgoto(const char *cap, int col, int row);The tgoto() function generates a cursor movement string based on the capability string cap, typically obtained from tgetstr(), and the target column col and row row. It returns a pointer to the resulting string that can be output to move the cursor accordingly. If an error occurs, it returns a string that may not be meaningful.
int tputs(const char *str, int affcnt, int (*putc)(int));The tputs() function outputs a string containing terminal control codes, typically obtained via tgetstr() or tgoto(). The str argument is the control string, affcnt is the number of affected lines (used for padding), and putc is a function to output each character (usually putchar). Returns 0 on success or a non-zero value if an error occurs during output.
int access(const char *pathname, int mode);The access() function checks a file's accessibility based on the calling process's real user and group IDs. The pathname is the file to check, and mode is a bitmask of permissions to test (R_OK, W_OK, X_OK, or F_OK to check existence). Returns 0 if the requested access is permitted, or -1 if it is not, setting errno accordingly.
int open(const char *pathname, int flags, mode_t mode);Parameters:
- pathname: The path to the file you want to open.
- flags: These are options that define the behavior of the open operation (e.g.,
O_RDONLY,O_WRONLY,O_RDWR,O_CREAT). - mode: The file permission settings, required when creating a new file (typically used with
O_CREAT). It's given in octal format (e.g., 0644). Return value: On success, it returns a file descriptor (a non-negative integer). On failure, it returns -1, and errno is set to indicate the error.
ssize_t read(int fd, void *buf, size_t count);The read() function attempts to read up to count bytes of data from the file descriptor fd into the buffer buf. It returns the number of bytes actually read (0 indicates end-of-file), or -1 on error, setting errno accordingly.
int close(int fd);The close() function closes the file descriptor fd, releasing any system resources associated with it. Once closed, the file descriptor can no longer be used. Returns 0 on success, or -1 on failure, setting errno accordingly.
pid_t fork(void);The fork() function creates a new process by duplicating the calling process. The new process (child) is an exact copy of the calling process (parent), except for the returned value. Returns 0 in the child process, the child's PID in the parent process, or -1 on failure, setting errno accordingly.
pid_t wait(int *wstatus);The wait() function suspends execution of the calling process until one of its child processes terminates. If wstatus is not NULL, it stores the exit status of the terminated child. Returns the PID of the terminated child on success, or -1 on failure (e.g., no child processes), setting errno accordingly.
pid_t waitpid(pid_t pid, int *wstatus, int options);The waitpid() function suspends execution of the calling process until the specified child process (pid) changes state. The pid can be a specific process ID, -1 to wait for any child, or other special values. The wstatus stores the exit status if not NULL, and options can modify behavior (e.g., WNOHANG, WUNTRACED). Returns the PID of the child that changed state, 0 if WNOHANG is set and no child has exited, or -1 on failure, setting errno.
pid_t wait3(int *wstatus, int options, struct rusage *rusage);The wait3() function suspends execution of the calling process until one of its child processes exits or a signal is received. It behaves like waitpid(-1, wstatus, options) but also provides resource usage information in the rusage structure if it is not NULL. Returns the PID of the terminated child, 0 if WNOHANG is specified and no child has exited, or -1 on failure, setting errno.
pid_t wait4(pid_t pid, int *wstatus, int options, struct rusage *rusage);The wait4() function is similar to waitpid() but additionally provides resource usage information for the terminated child process in the rusage structure. The pid specifies which child to wait for, wstatus stores the exit status, and options can modify behavior (e.g., WNOHANG, WUNTRACED). Returns the PID of the child that changed state, 0 if WNOHANG is set and no child has exited, or -1 on failure, setting errno.
__sighandler_t signal(int signum, __sighandler_t handler);The signal() function sets a handler function for a specific signal signum (e.g., SIGINT, SIGTERM). The handler can be a function pointer to a custom handler, or the constants SIG_IGN (ignore) or SIG_DFL (default action). Returns the previous signal handler on success, or SIG_ERR on failure, setting errno accordingly.
int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);The sigaction() function allows precise control over the handling of signals. It sets a new action for the signal signum using the act structure, and optionally stores the previous action in oldact if it is not NULL. This function is preferred over signal() for reliable and portable signal handling. Returns 0 on success, or -1 on failure, setting errno accordingly.
int sigemptyset(sigset_t *set);The sigemptyset() function initializes the signal set pointed to by set, clearing all signals from it. This means the set will contain no signals after the call. It is typically used before adding specific signals to the set. Returns 0 on success, or -1 on failure, setting errno accordingly.
int sigaddset(sigset_t *set, int signum);The sigaddset() function adds the signal signum to the signal set pointed to by set. This is used to build a set of signals for blocking or handling operations. Returns 0 on success, or -1 on failure, setting errno accordingly.
int kill(pid_t pid, int sig);The kill() function sends the signal sig to the process specified by pid. If pid is greater than 0, the signal is sent to that specific process. Special values like 0, -1, or negative group IDs can target process groups or all processes the caller has permission to signal. Returns 0 on success, or -1 on failure, setting errno accordingly.
void exit(int status);The exit() function terminates the calling process immediately, returning the exit status to the parent process. It performs cleanup operations such as flushing standard I/O buffers and calling functions registered with atexit(). The status code is typically used to indicate success (0) or failure (non-zero).
int printf(const char *format, ...);printf prints formatted output to the standard output (usually the terminal).
void *malloc(size_t size);malloc in C is used to dynamically allocate memory at runtime from the heap.
void free(void *ptr);The free function in C is used to deallocate memory that was previously allocated with malloc, calloc, or realloc. It's essential for preventing memory leaks.
ssize_t write(int fd, const void *buf, size_t count);The write() function writes up to count bytes from the buffer buf to the file descriptor fd. It returns the number of bytes actually written, which may be less than count, or -1 on failure, setting errno accordingly.