// commands.go - встроенные команды fush shell (стиль busybox) // Реализует базовые команды: exit, ls, cd, mkdir, rm, touch, pwd, cat, echo // Все команды работают независимо от ОС, используя только стандартную библиотеку Go package shell import ( "bufio" "fmt" "io" "os" "path/filepath" "strings" "time" "fush/pkg/ansi" ) // cmdExit обрабатывает команду exit func (s *Shell) cmdExit(args []string) error { s.running.Store(false) s.logger.Info("Выполнена команда exit") return nil } // cmdHelp обрабатывает команду help func (s *Shell) cmdHelp(args []string) error { fmt.Println() ansi.Println(ansi.Cyan, "╔══════════════════════════════════════════════════════════════╗") ansi.Println(ansi.Cyan, "║ fush shell - Доступные команды ║") ansi.Println(ansi.Cyan, "╚══════════════════════════════════════════════════════════════╝") fmt.Println() ansi.Println(ansi.Yellow, "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━") ansi.Println(ansi.BrightGreen, "ВСТРОЕННЫЕ КОМАНДЫ (BUSYBOX-STYLE):") ansi.Println(ansi.Yellow, "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━") commands := []struct { name string desc string }{ {"exit", "Выход из оболочки"}, {"help", "Показать эту справку"}, {"ls [path]", "Вывести список файлов в директории"}, {"cd [dir]", "Сменить текущую директорию"}, {"pwd", "Показать текущую директорию"}, {"mkdir [-p] ", "Создать новую директорию"}, {"rm [-rf] ", "Удалить файл или директорию"}, {"touch ", "Создать файл или обновить время доступа"}, {"cat ", "Вывести содержимое файла"}, {"echo [text...]", "Вывести текст на экран"}, {"exec [args...]", "Выполнить внешнюю команду"}, } for _, cmd := range commands { fmt.Printf(" %-20s %s\n", ansi.Colorize(cmd.name, ansi.BrightWhite), cmd.desc) } fmt.Println() ansi.Println(ansi.Yellow, "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━") ansi.Println(ansi.BrightGreen, "ПРИМЕРЫ ИСПОЛЬЗОВАНИЯ:") ansi.Println(ansi.Yellow, "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━") fmt.Printf(" %-30s %s\n", ansi.Colorize("ls -la", ansi.BrightWhite), "Показать все файлы") fmt.Printf(" %-30s %s\n", ansi.Colorize("cd /tmp", ansi.BrightWhite), "Перейти в /tmp") fmt.Printf(" %-30s %s\n", ansi.Colorize("pwd", ansi.BrightWhite), "Показать текущий путь") fmt.Printf(" %-30s %s\n", ansi.Colorize("mkdir -p a/b/c", ansi.BrightWhite), "Создать вложенные директории") fmt.Printf(" %-30s %s\n", ansi.Colorize("rm -rf olddir", ansi.BrightWhite), "Удалить директорию рекурсивно") fmt.Printf(" %-30s %s\n", ansi.Colorize("cat file.txt", ansi.BrightWhite), "Показать содержимое файла") fmt.Printf(" %-30s %s\n", ansi.Colorize("echo Hello World", ansi.BrightWhite), "Вывести текст") fmt.Printf(" %-30s %s\n", ansi.Colorize("exec go version", ansi.BrightWhite), "Выполнить внешнюю команду") fmt.Printf(" %-30s %s\n", ansi.Colorize("ls | grep .go", ansi.BrightWhite), "Пайплайн") fmt.Printf(" %-30s %s\n", ansi.Colorize("ls > files.txt", ansi.BrightWhite), "Перенаправление вывода") fmt.Println() ansi.Println(ansi.Yellow, "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━") return nil } // cmdLs обрабатывает команду ls (стиль busybox) func (s *Shell) cmdLs(args []string) ([]byte, error) { path := "." showAll := false longFormat := false // Парсим аргументы for _, arg := range args { if arg == "-a" || arg == "--all" { showAll = true } else if arg == "-l" { longFormat = true } else if !strings.HasPrefix(arg, "-") { path = arg } } dir, err := os.Open(path) if err != nil { return nil, err } defer dir.Close() entries, err := dir.Readdir(-1) if err != nil { return nil, err } var output strings.Builder for _, entry := range entries { name := entry.Name() // Пропускаем скрытые файлы если не указан -a if !showAll && strings.HasPrefix(name, ".") { continue } if longFormat { // Формат: права ссылки владелец группа размер дата имя perms := entry.Mode().String() nlink := 1 // В Go сложно получить количество жестких ссылок uid := fmt.Sprintf("%d", entry.Sys() != nil) // Упрощённо gid := "users" size := entry.Size() modTime := entry.ModTime().Format("Jan _2 15:04") if entry.IsDir() { name = name + "/" } else if entry.Mode()&os.ModeSymlink != 0 { // Для симлинков пытаемся прочитать цель if target, err := os.Readlink(filepath.Join(path, entry.Name())); err == nil { name = name + " -> " + target } } fmt.Fprintf(&output, "%s %3d %-8s %-8s %8d %s %s\n", perms, nlink, uid, gid, size, modTime, name) } else { if entry.IsDir() { name = name + "/" } fmt.Fprint(&output, name, "\n") } } return []byte(output.String()), nil } // cmdCd обрабатывает команду cd func (s *Shell) cmdCd(args []string) error { path := s.GetEnv("HOME") if len(args) > 0 { path = args[0] } if path == "~" { path = s.GetEnv("HOME") } if err := os.Chdir(path); err != nil { return err } pwd, err := os.Getwd() if err == nil { s.SetEnv("PWD", pwd) } return nil } // cmdPwd обрабатывает команду pwd func (s *Shell) cmdPwd(args []string) ([]byte, error) { dir, err := os.Getwd() if err != nil { return nil, err } return []byte(dir + "\n"), nil } // cmdMkdir обрабатывает команду mkdir (с поддержкой -p) func (s *Shell) cmdMkdir(args []string) error { if len(args) == 0 { return fmt.Errorf("требуется имя директории") } createParents := false var dirs []string for _, arg := range args { if arg == "-p" || arg == "--parents" { createParents = true } else if !strings.HasPrefix(arg, "-") { dirs = append(dirs, arg) } } for _, dir := range dirs { var err error if createParents { err = os.MkdirAll(dir, 0755) } else { err = os.Mkdir(dir, 0755) } if err != nil { return err } } return nil } // cmdRm обрабатывает команду rm (с поддержкой -r и -f) func (s *Shell) cmdRm(args []string) error { if len(args) == 0 { return fmt.Errorf("требуется имя файла") } recursive := false force := false var targets []string for _, arg := range args { switch arg { case "-r", "-R", "--recursive": recursive = true case "-f", "--force": force = true default: if !strings.HasPrefix(arg, "-") { targets = append(targets, arg) } } } for _, target := range targets { info, err := os.Stat(target) if err != nil { if !force { return err } continue } if info.IsDir() && !recursive { return fmt.Errorf("'%s' является директорией, используйте -r для удаления", target) } if err := os.RemoveAll(target); err != nil && !force { return err } } return nil } // cmdTouch обрабатывает команду touch func (s *Shell) cmdTouch(args []string) error { if len(args) == 0 { return fmt.Errorf("требуется имя файла") } now := time.Now() for _, filename := range args { if strings.HasPrefix(filename, "-") { continue } file, err := os.OpenFile(filename, os.O_CREATE|os.O_WRONLY, 0644) if err != nil { return err } file.Close() if err := os.Chtimes(filename, now, now); err != nil { return err } } return nil } // cmdCat обрабатывает команду cat (конкатенация файлов) func (s *Shell) cmdCat(args []string) ([]byte, error) { if len(args) == 0 { // Читаем из stdin info, err := os.Stdin.Stat() if err != nil { return nil, err } if info.Mode()&os.ModeCharDevice != 0 { return nil, fmt.Errorf("ожидается файл или stdin") } reader := bufio.NewReader(os.Stdin) var output strings.Builder for { line, err := reader.ReadString('\n') if err == io.EOF { break } if err != nil { return nil, err } output.WriteString(line) } return []byte(output.String()), nil } var output strings.Builder for _, filename := range args { if strings.HasPrefix(filename, "-") { continue } data, err := os.ReadFile(filename) if err != nil { return nil, fmt.Errorf("ошибка чтения '%s': %v", filename, err) } output.Write(data) if len(data) > 0 && data[len(data)-1] != '\n' { output.WriteByte('\n') } } return []byte(output.String()), nil } // cmdEcho обрабатывает команду echo func (s *Shell) cmdEcho(args []string) ([]byte, error) { newline := true startIdx := 0 if len(args) > 0 && args[0] == "-n" { newline = false startIdx = 1 } var output strings.Builder for i := startIdx; i < len(args); i++ { if i > startIdx { output.WriteByte(' ') } output.WriteString(args[i]) } if newline { output.WriteByte('\n') } return []byte(output.String()), nil }