Functional Requirements#
- Achieve effects similar to the native Linux command [ls -al]
- Required information includes: file information, link count, username, group name, file size, modification time, file name
- Additional implementations: file sorting, color beautification, display of soft links
Final Effect#
- Basic template of ls -al has been implemented
Implementation Process#
Thought Process Flowchart#
Getting Command Line Parameters#
Capture the options of ls -al and optional option parameter path
- Identify ls -al, ls -l, ls -al path, ls -l path, and report errors for others
#include "head.h"
void myls(char **argv, char *path, int a_flag) {
if (a_flag) printf("run %s -al %s\n", argv[0], path);
else printf("run %s -l %s\n", argv[0], path);
return ;
}
void show_tips(char **argv) {
fprintf(stderr, "Usage: %s -[a]l [path]\n", argv[0]);
return ;
}
int main(int argc, char **argv) {
// Without path parameter [default is current directory]
if (argc == 2){
if (!strcmp(argv[1], "-al")) myls(argv, ".", 1);
else if (!strcmp(argv[1], "-l")) myls(argv, ".", 0);
else show_tips(argv), exit(1);
return 0;
}
int opt;
int a_flag = 0; // Determine -a option
// With path parameter
while ((opt = getopt(argc, argv, "al:")) != -1) {
switch (opt) {
case 'a': {
a_flag = 1;
} break;
case 'l': {
myls(argv, optarg, a_flag);
} break;
default: {
show_tips(argv);
exit(1);
}
}
}
return 0;
}
- Because in getopt, for optional option parameters, the option [option followed by "::"] must have its parameter immediately following it [e.g., ls -al123], and cannot be separated by a space like the native ls -al path [e.g., ls -al 123], so the option parameter is set as required [option followed by ":"], and a check is made for cases without option parameters
- The header file head.h is at the end
- Use the test script test.sh [see end] for testing, the effect is as follows:
- Can already capture and identify ls -al, ls -l, ls -al path, ls -l path, as well as other erroneous inputs
Reading Directory#
Read the list of file names in the directory
- Use opendir, readdir [later changed to scandir for sorting convenience, see sorting display]
- Just modify the myls() function, the code is as follows
void myls(char *path, int a_flag) {
DIR *dirp;
// 0. Open and read the directory
if ((dirp = opendir(path)) != NULL) {
struct dirent *dent;
while (dent = readdir(dirp)) {
if (dent->d_name[0] == '.' && !a_flag) continue;
printf("%s\n", dent->d_name);
}
} else {
perror("opendir");
exit(1);
}
return ;
}
- At this point, myls no longer needs the argv parameter
- Determine whether to display hidden files based on the presence of the a option
- Any file name starting with "." is a hidden file
- Some test effects are as follows
- Can already read all file names in the directory and distinguish hidden files
Reading and Processing File Information⭐#
Get file information based on each file's name
lstat to Get Basic File Information#
- Use [lstat] to get basic file information [file permissions, hard link count, uid, gid, file size, modification time]
- Memory must be allocated for the created stat structure, not creating a structure pointer, refer to C - what is the difference between struct stat * buffer and &buffer in linux stat function
- For directories or files not in the current path, you need to access them using the absolute path obtained by path concatenation [or chdir to switch to the specified directory directly]
- Otherwise, lstat cannot locate the file path
// Path concatenation to obtain absolute path
void absolute_path(char *ab_path, char* path, char *filename) {
strcpy(ab_path, path); // Keep the original path value
strcat(ab_path, "/"); // Note to add path separator
strcat(ab_path, filename); // Concatenate
return ;
}
Friendly Display of File Information#
- File information <file type, file [special] permissions> 👉 character form
// Get friendly file information
void mode2str(mode_t smode, char *mode) {
int i = 0;
// rwx array, corresponding to 000, 001, ..., 110, 111
char *rwx[] = {
"---", "--x", "-w-",
"-wx", "r--", "r-x",
"rw-", "rwx"
};
// Get file type
if (S_ISREG(smode)) mode[0] = '-';
else if (S_ISDIR(smode)) mode[0] = 'd';
else if (S_ISBLK(smode)) mode[0] = 'b';
else if (S_ISCHR(smode)) mode[0] = 'c';
#ifdef S_ISFIFO
else if (S_ISFIFO(smode)) mode[0] = 'p';
#endif
#ifdef S_ISLNK
else if (S_ISLNK(smode)) mode[0] = 'l';
#endif
#ifdef S_ISSOCK
else if (S_ISSOCK(smode)) mode[0] = 's';
#endif
else mode[i++] = '?';
// Get file permissions
strcpy(&mode[1], rwx[(smode >> 6) & 7]);
strcpy(&mode[4], rwx[(smode >> 3) & 7]);
strcpy(&mode[7], rwx[smode & 7]);
// Get file special permissions
if (smode & S_ISUID) mode[3] = (smode & S_IXUSR) ? 's' : 'S';
if (smode & S_ISGID) mode[6] = (smode & S_IXGRP) ? 's' : 'S';
if (smode & S_ISVTX) mode[9] = (smode & S_IXOTH) ? 't' : 'T';
return ;
}
- The idea refers to Printing file permissions like 'ls -l' using stat(2) in C——StackOverflow
- This article uses the solution from the second answer, creating an rwx array and cleverly using bitwise operations
- Details refer to stat structure st_mode field, Using st_mode to determine file type in Linux——CSDN
- The meaning of each bit is very clear
- Macro definitions can refer to the man page: man inode, search for st_mode
Friendly Display of Modification Time#
- Timestamp 👉 friendly readable time
// Get friendly mtime
void mtim2str(struct timespec *mtim, char *str, size_t max) {
struct tm *tmp_time = localtime(&mtim->tv_sec); // Convert to local calendar time
strftime(str, max, "%b %e %H:%M", tmp_time); // Convert to specified format
return ;
}
- Timestamp → time structure → specified format
- The first step conversion is completed through localtime—cppreference to obtain the tm structure
- The second step conversion is completed through strftime—cppreference to obtain the string
- Refer to Timestamps and Time Formats in C——Jianshu
- [Alternatively] refer to the example in the man page, using ctime—cppreference, and then extract the substring
Get Owner Username and Group Name#
4, 5. Obtain the file's [username, group name] through [getpwuid, getgrgid] with uid, gid
strcpy(uname, getpwuid(st.st_uid)->pw_name); // 4. Get the file owner's name
strcpy(gname, getgrgid(st.st_gid)->gr_name); // 5. Get the file's group name
- The effect is as follows:
- Only tools that are unimaginable can be tried to implement the process of obtaining names through uid, gid
Sorting Display#
- See the complete code at the end
- readdir reads directory files in a disordered manner, while scandir can read the file list in dictionary order
- Refer to the EXAMPLE of man scandir
- Create a file list, read in dictionary order, and output in reverse order
- scandir completes the work of opendir and readdir, so replace opendir and readdir with scandir, and then use d_name for lstat
- The effect is as follows
- The sorting rules are slightly different, and next, control the color and display the soft link target
Color Control#
- See the complete code at the end [Color macros see head.h]
- Mainly beautified the colors for directories, soft links, executable files, and some special permission files
- Other colors are similar
- The effect is as follows
- Then implement the display of soft link targets
Display of Soft Link Targets#
- See the complete code at the end
- Use readlink to read the source file name pointed to by the soft link, refer to man readlink
- The effect is as follows
- 🔚
Complete Code#
myls.c#
#include "head.h"
// Path concatenation to obtain absolute path
void absolute_path(char *ab_path, char* path, char *filename) {
strcpy(ab_path, path); // Keep the original path value
strcat(ab_path, "/"); // Note to add path separator
strcat(ab_path, filename); // Concatenate
return ;
}
// Get friendly file information
void mode2str(mode_t smode, char *mode, char *color) {
int i = 0;
// rwx array, corresponding to 000, 001, ..., 110, 111
char *rwx[] = {
"---", "--x", "-w-",
"-wx", "r--", "r-x",
"rw-", "rwx"
};
// Get file type
if (S_ISREG(smode)) mode[0] = '-';
else if (S_ISDIR(smode)) mode[0] = 'd', strcpy(color, "BLUE_HL");
else if (S_ISBLK(smode)) mode[0] = 'b';
else if (S_ISCHR(smode)) mode[0] = 'c';
#ifdef S_ISFIFO
else if (S_ISFIFO(smode)) mode[0] = 'p';
#endif
#ifdef S_ISLNK
else if (S_ISLNK(smode)) mode[0] = 'l', strcpy(color, "BLUE");
#endif
#ifdef S_ISSOCK
else if (S_ISSOCK(smode)) mode[0] = 's';
#endif
else mode[i++] = '?';
// Get file permissions
strcpy(&mode[1], rwx[(smode >> 6) & 7]);
strcpy(&mode[4], rwx[(smode >> 3) & 7]);
strcpy(&mode[7], rwx[smode & 7]);
// Get file special permissions
if (smode & S_ISUID) mode[3] = (smode & S_IXUSR) ? 's' : 'S';
if (smode & S_ISGID) mode[6] = (smode & S_IXGRP) ? 's' : 'S';
if (smode & S_ISVTX) mode[9] = (smode & S_IXOTH) ? 't' : 'T';
// + Configure color [also above]
if (mode[0] == '-') {
if (strstr(mode, "x")) strcpy(color, "GREEN_HL");
if (strstr(mode, "s") || strstr(mode, "S")) strcpy(color, "YELLOW_BG");
}
return ;
}
// Get friendly mtime
void mtim2str(struct timespec *mtim, char *str, size_t max) {
struct tm *tmp_time = localtime(&mtim->tv_sec); // Convert to local calendar time
strftime(str, max, "%b %e %H:%M", tmp_time); // Convert to specified format
return ;
}
void myls(char *path, int a_flag) {
// 0. Read the directory's file list in [dictionary order]
struct dirent **namelist;
int n, i = -1;
n = scandir(path, &namelist, NULL, alphasort); // Read in dictionary order
if (n == -1) {
perror("scandir");
exit(1);
}
// Traverse the file list in order
while (i < n - 1) {
i++;
struct dirent *dent = namelist[i];
if (dent->d_name[0] == '.' && !a_flag) continue; // Whether to display hidden files
struct stat st; // Memory must be allocated for this structure
char ab_path[128]; // Record absolute path
absolute_path(ab_path, path, dent->d_name); // +. Path concatenation
// 1. Read file information
if (!lstat(ab_path, &st)) {
char mode[16], mtime[32], uname[16], gname[16], filename[512], color[16] = {0};
mode2str(st.st_mode, mode, color); // 2. Process file information [and set color]
mtim2str(&st.st_mtim, mtime, sizeof(mtime)); // 3. Process modification time
strcpy(uname, getpwuid(st.st_uid)->pw_name); // 4. Get the file owner's name
strcpy(gname, getgrgid(st.st_gid)->gr_name); // 5. Get the file's group name
// Print basic information except file name
printf("%s %lu %s %s %*lu %s ",
mode, st.st_nlink, uname, gname,
5, st.st_size, mtime
);
// 6. Wrap file name according to color
if (!strcmp(color, "BLUE")) sprintf(filename, BLUE("%s"), dent->d_name);
else if (!strcmp(color, "BLUE_HL")) sprintf(filename, BLUE_HL("%s"), dent->d_name);
else if (!strcmp(color, "GREEN_HL")) sprintf(filename, GREEN_HL("%s"), dent->d_name);
else if (!strcmp(color, "YELLOW_BG")) sprintf(filename, YELLOW_BG("%s"), dent->d_name);
else strcpy(filename, dent->d_name);
// 7. Special handling for soft links
if (mode[0] == 'l') {
char linkfile[32];
readlink(ab_path, linkfile, 32);
strcat(filename, " -> ");
strcat(filename, linkfile);
}
// Print file name separately [including color]
printf("%s\n", filename);
} else {
perror("lstat");
exit(1);
}
free(namelist[i]);
}
free(namelist);
return ;
}
void show_tips(char **argv) {
fprintf(stderr, "Usage: %s -[a]l [path]\n", argv[0]);
return ;
}
int main(int argc, char **argv) {
// Without path parameter [default is current directory]
if (argc == 2){
if (!strcmp(argv[1], "-al")) myls(".", 1);
else if (!strcmp(argv[1], "-l")) myls(".", 0);
else show_tips(argv), exit(1);
return 0;
}
int opt, a_flag = 0; // a_flag: determine -a option
// With path parameter
while ((opt = getopt(argc, argv, "al:")) != -1) {
switch (opt) {
case 'a': {
a_flag = 1;
} break;
case 'l': {
myls(optarg, a_flag);
} break;
default: {
show_tips(argv);
exit(1);
}
}
}
return 0;
}
- See comments for details
- Column spacing can be controlled using printf("%*s", len, "xyz") format, with len controlling the value of *, len is adjusted based on string length [not considered]
- The sorting display changes from opendir + readdir to scandir, which can read files in order into a string array
- Color control
- ❗ When setting colors in mode2str, the assignment of the color character array should use strcpy; if directly assigned with "=", the value assigned to color in the function will not exit the function, even if color is a character pointer. Therefore, use the color character array with the strcpy function [here color adopts the output parameter method]
- Use strcmp for string comparison, not "=="
- To facilitate the binding of file name colors and file names, create a 512 byte size character array filename to encapsulate the file name and corresponding color
- Using 512 is because in sprintf, the compiler considers a maximum of 256 bytes
- 🆒 There is a more elegant solution—using snprintf and dynamically allocating space based on data, refer to Detecting String Truncation with GCC 8
- malloc is the essence
- Display of soft link targets
- The generation of filename originates from this, with the arrow pointing to the original file displayed in the default color
head.h#
#ifndef _HEAD_H
#define _HEAD_H
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <dirent.h>
#include <sys/stat.h>
#include <time.h>
#include <pwd.h>
#include <grp.h>
#define COLOR(a, b) "\033[" #b "m" a "\033[0m"
#define COLOR_BG(a, b) "\033[2;" #b "m" a "\033[0m"
#define COLOR_HL(a, b) "\033[1;" #b "m" a "\033[0m"
#define RED(a) COLOR(a, 31)
#define GREEN(a) COLOR(a, 32)
#define YELLOW(a) COLOR(a, 33)
#define BLUE(a) COLOR(a, 34)
#define PURPLE(a) COLOR(a, 35)
#define RED_HL(a) COLOR_HL(a, 31)
#define GREEN_HL(a) COLOR_HL(a, 32)
#define YELLOW_HL(a) COLOR_HL(a, 33)
#define BLUE_HL(a) COLOR_HL(a, 34)
#define PURPLE_HL(a) COLOR_HL(a, 35)
#define RED_BG(a) COLOR_BG(a, 41)
#define GREEN_BG(a) COLOR_BG(a, 42)
#define YELLOW_BG(a) COLOR_BG(a, 43)
#define BLUE_BG(a) COLOR_BG(a, 44)
#define PURPLE_BG(a) COLOR_BG(a, 45)
#endif
test.sh#
#!/bin/bash
path="x.TestDir"
echo "./ls -al:" | lolcat
./ls -al
echo "./ls -l:" | lolcat
./ls -l
echo "./ls -al $path:" | lolcat
./ls -al $path
echo "./ls -l $path:" | lolcat
./ls -l $path
echo "./ls -b:" | lolcat
./ls -b
echo "./ls -b $path:" | lolcat
./ls -b $path
- ./ls is the executable file name generated using gcc ... -o ls
[PS]
- How to view what format control strings correspond to complex structure member variables?
- Such as %s, %lu...
- ① Write one casually and see the error message prompt
- ② Or use ctags to view the source code: ctrl + ]
References#
- The overall idea refers to: 《1 File, Directory Operations and the Idea of Implementing ls—Basic Idea of Implementing ls -al》