358 lines
10 KiB
Go
358 lines
10 KiB
Go
|
|
// 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
|
|||
|
|
}
|