Files
fush/internal/shell/commands.go

358 lines
10 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 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] <dir>", "Создать новую директорию"},
{"rm [-rf] <file>", "Удалить файл или директорию"},
{"touch <file>", "Создать файл или обновить время доступа"},
{"cat <file>", "Вывести содержимое файла"},
{"echo [text...]", "Вывести текст на экран"},
{"exec <cmd> [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
}