開発:私自身はshellをとてもシンプルにしか使わないのですが、一方でヒストリー機能とリソース使用量の記録は非常に重要だと思っています。それで、どのコマンドをどこで実行してどのくらいのリソース使用だったかというのを記録することにしました。あと、さっきいたあのディレクトリに戻りたいということが多いので、作業ディレクトリのヒストリを作ることにしました。
基盤:どのディレクトリでどのくらいリソースを使う仕事をしたとか、そのディレクトリでのアクティブな滞在時間がわかると良いですね。まあそれも、コマンドのヒストリーのほうから集計すれば良いのかも知れませんが。
開発:ただ、長大なコマンドヒストリを探したり編集したりをラインエディタでやるのはどうかと思います。なので、そういう作業はこのgshellをHTTPサーバにして、HTTPクライアントというかブラウザからやればよいのかなと。
開発:あとこれは各コマンドの出力も後から読めるようにしたいです。デフォルトで、コマンド単位でsciriptコマンドが動いているみたいな。で、それを閲覧するのもやはり、ブラウザが適していそうです。
社長:リモートにログインした状態でそれをやるには、リモートのgshellサーバに繋がないといけないですが、ローカルな出向元のshellの口から連絡したいですよね。そういう意味でも、リモートシェル機能は内蔵していないといけない。
開発:?はファイル名の1文字のマッチングに使われてしまってますからねぇ… でもまあ、行頭の?だけは別扱いでも良いかもしません。ふつうに?として使いたかったら、./?とかすれば良さどうです。
社長:ディレクトリがタブでコンプリーションできると良いですね。というか、このへんはかな漢字変換のシンプル版みたいなものですから、実際かな漢字変換と同じキーバインディングで検索すればよいのかなと思いますが。
開発:あと、gshell 自体が検索したファイルとか、に対しては、find の -ls 的に表示できるようにしたいと思っています。まずはwhichでやってみました。
社長:whichでやる指定されたPATHの検索とfindは兄弟みたいなものだし、lsは find -ls の簡略版みたいものだと思います。PATHの中でファイルをワイルドカードで検索する機能はもともと動的ライブラリのために存在しますから、それをユーザがコマンドとして、あるいは引数のワイルドカードとして使えるようにすると良いと思いますね。
開発:PATHをいちいち定義するのは面倒だったりするので、これまでに滞在したディレクトリを動的にPATHとして使えれば良いですね。あるいは、これまでプログラムに渡したファイル名と思しき引数の中から検索させる、というかオートコンプリーションさせる。
社長:コンピュータの黎明期にはヒストリ機能というのはリソースを食う贅沢機能だったわけです。個人が使えるディスク容量が1メガも無い時代。コマンドシェルって、そういう時代の錯誤を引きずってる気がしますね。
開発:ヒストリの検索をしやすくするためには、ユーザが明示的にラベルと言うかタグを付けたいですね。というか、ヒストリをHTTPで、HTMLで眺めるという全体なら、ログインごとにタイトルとかサブタイトルを付ける必要はあるのかなと思います。ブックマークといいいますか。
開発:だいぶGoの書き方というか、パッケージのドキュメントの流儀とか、スライスの使い方がわかって来たのでスムーズになりました。ただ今作っているのがシステム寄りのコードなのですが、os パッケージが非力というか未熟なのがつらいですかね。syscall になることが多いです。
gsh0.0.5
//Gshell by SatoxITS
// gsh - Go lang based Shell
// (c) 2020 ITS more Co., Ltd.
// 2020-0807 created by SatoxITS (sato@its-more.jp)
//
package main // gsh main
// Documents: https://golang.org/pkg/
import (
"bufio"
"strings"
"strconv"
"sort"
"fmt"
"os"
"time"
"syscall"
"go/types"
"go/token"
"net"
)
var VERSION = "gsh/0.0.5 (2020-0810a)"
var LINESIZE = (8*1024)
var PATHSEP = ":" // should be ";" in Windows
var DIRSEP = "/" // canbe \ in Windows
var PROMPT = "> "
var GSH_HOME = ".gsh" // under home directory
type GCommandHistory struct {
StartAt time.Time
EndAt time.Time
ResCode int
OutData *os.File
CmdLine string
ResCons int // Resource consumption, CPU time or so
}
type GChdirHistory struct {
Dir string
MovedAt time.Time
}
type GshContext struct {
StartDir string // the current directory at the start
GetLine string // gsh-getline command as a input line editor
ChdirHistory []GChdirHistory // the 1st entry is wd at the start
gshPA syscall.ProcAttr
CommandHistory []GCommandHistory
BackGround bool
BackGroundJobs []int
LastRusage syscall.Rusage
GshHomeDir string
TerminalId int
}
func isin(what string, list []string) bool {
for _, v := range list {
if v == what {
return true
}
}
return false
}
func env(opts []string) {
env := os.Environ()
if isin("-s", opts){
sort.Slice(env, func(i,j int) bool {
return env[i] < env[j]
})
}
for _, v := range env {
fmt.Printf("%v\n",v)
}
}
func strsubst(str string) string {
rstr := ""
inEsc := 0 // escape characer mode
for _, ch := range str {
if inEsc == 0 {
if ch == '\\' {
inEsc = '\\'
continue;
}
}
if inEsc == '\\' {
if ch == 's' { ch = ' ' }
if ch == 'r' { ch = '\r' }
if ch == 'n' { ch = '\n' }
if ch == 't' { ch = '\t' }
if ch == '\\' { ch = '\\' }
inEsc = 0
}
rstr = rstr + string(ch)
}
return rstr
}
func showFileInfo(path string, opts []string) {
if isin("-ls",opts) {
fi, _ := os.Stat(path)
mod := fi.ModTime()
date := mod.Format(time.Stamp)
fmt.Printf("%v %8v %s ",fi.Mode(),fi.Size(),date)
}
fmt.Printf("%s",path)
if ! isin("-n",opts) {
fmt.Printf("\n")
}
}
func toFullpath(path string) (fullpath string) {
pathv := strings.Split(path,DIRSEP)
if pathv[0] == "." {
pathv[0], _ = os.Getwd()
}else
if pathv[0] == ".." {
cwd, _ := os.Getwd()
ppathv := strings.Split(cwd,DIRSEP)
pathv[0] = strings.Join(ppathv,DIRSEP)
}else
if pathv[0] == "~" {
pathv[0],_ = os.UserHomeDir()
}
return strings.Join(pathv,DIRSEP)
}
func which(list string, argv []string) (fullpathv []string, itis bool){
if len(argv) <= 1 {
fmt.Printf("Usage: which comand [-s] [-a] [-ls]\n")
return []string{""}, false
}
path := argv[1]
pathenv, efound := os.LookupEnv(list)
if ! efound {
fmt.Printf("which: no \"%s\" environment\n",list)
return []string{""}, false
}
dirv := strings.Split(pathenv,PATHSEP)
ffound := false
ffullpath := path
for _, dir := range dirv {
fullpath := dir + DIRSEP + path
fi, err := os.Stat(fullpath)
if err != nil {
fullpath = dir + DIRSEP + path + ".go"
fi, err = os.Stat(fullpath)
}
if err == nil {
fm := fi.Mode()
if fm.IsRegular() {
ffullpath = fullpath
ffound = true
if ! isin("-s", argv) {
showFileInfo(fullpath,argv)
}
if ! isin("-a", argv) {
break;
}
}
}
}
return []string{ffullpath}, ffound
}
func find(argv []string){
}
func eval(argv []string, nlend bool){
var ai = 1
pfmt := "%s"
if argv[ai][0:1] == "%" {
pfmt = argv[ai]
ai = 2
}
if len(argv) <= ai {
return
}
gocode := strings.Join(argv[ai:]," ");
fset := token.NewFileSet()
rval, _ := types.Eval(fset,nil,token.NoPos,gocode)
fmt.Printf(pfmt,rval.Value)
if nlend { fmt.Printf("\n") }
}
func getval(name string) (found bool, val int) {
/* should expand the name here */
if name == "gsh.pid" {
return true, os.Getpid()
}else
if name == "gsh.ppid" {
return true, os.Getppid()
}
return false, 0
}
func echo(argv []string, nlend bool){
for ai := 1; ai < len(argv); ai++ {
if 1 < ai {
fmt.Printf(" ");
}
arg := argv[ai]
found, val := getval(arg)
if found {
fmt.Printf("%d",val)
}else{
fmt.Printf("%s",arg)
}
}
if nlend {
fmt.Printf("\n");
}
}
func resfile() string {
return "gsh.tmp"
}
//var resF *File
func resmap() {
//_ , err := os.OpenFile(resfile(), os.O_RDWR|os.O_CREATE, os.ModeAppend)
// https://developpaper.com/solution-to-golang-bad-file-descriptor-problem/
_ , err := os.OpenFile(resfile(), os.O_RDWR|os.O_CREATE, 0600)
if err != nil {
fmt.Printf("refF could not open: %s\n",err)
}else{
fmt.Printf("refF opened\n")
}
}
func excommand(gshCtx GshContext, exec bool, argv []string) (GshContext, bool) {
gshPA := gshCtx.gshPA
fullpathv, itis := which("PATH",[]string{"which",argv[0],"-s"})
if itis == false {
return gshCtx, true
}
fullpath := fullpathv[0]
if 0 < strings.Index(fullpath,".go") {
nargv := argv // []string{}
gofullpathv, itis := which("PATH",[]string{"which","go","-s"})
if itis == false {
fmt.Printf("--F-- Go not found\n")
return gshCtx, true
}
gofullpath := gofullpathv[0]
nargv = []string{ gofullpath, "run", fullpath }
fmt.Printf("--I-- %s {%s %s %s}\n",gofullpath,
nargv[0],nargv[1],nargv[2])
if exec {
syscall.Exec(gofullpath,nargv,os.Environ())
}else{
pid, _ := syscall.ForkExec(gofullpath,nargv,&gshPA)
if gshCtx.BackGround {
fmt.Printf("--I-- in Background [%d]\n",pid)
gshCtx.BackGroundJobs = append(gshCtx.BackGroundJobs,pid)
}else{
rusage := syscall.Rusage {}
syscall.Wait4(pid,nil,0,&rusage)
gshCtx.LastRusage = rusage
}
}
}else{
if exec {
syscall.Exec(fullpath,argv,os.Environ())
}else{
pid, _ := syscall.ForkExec(fullpath,argv,&gshPA)
//fmt.Printf("[%d]\n",pid); // '&' to be background
if gshCtx.BackGround {
fmt.Printf("--I-- in Background [%d]\n",pid)
gshCtx.BackGroundJobs = append(gshCtx.BackGroundJobs,pid)
}else{
rusage := syscall.Rusage {}
syscall.Wait4(pid,nil,0,&rusage);
gshCtx.LastRusage = rusage
}
}
}
return gshCtx, false
}
func sleep(gshCtx GshContext, gshPA syscall.ProcAttr, argv []string) {
if len(argv) < 2 {
fmt.Printf("Sleep 100ms, 100us, 100ns, ...\n")
return
}
duration := argv[1];
d, err := time.ParseDuration(duration)
if err != nil {
d, err = time.ParseDuration(duration+"s")
if err != nil {
fmt.Printf("duration ? %s (%s)\n",duration,err)
return
}
}
fmt.Printf("Sleep %v ns\n",duration)
time.Sleep(d)
if 0 < len(argv[2:]) {
gshellv(gshCtx, gshPA, argv[2:])
}
}
func repeat(gshCtx GshContext, gshPA syscall.ProcAttr, argv []string) {
if len(argv) < 2 {
return
}
start0 := time.Now()
for ri,_ := strconv.Atoi(argv[1]); 0 < ri; ri-- {
if 0 < len(argv[2:]) {
//start := time.Now()
gshellv(gshCtx, gshPA, argv[2:])
end := time.Now()
elps := end.Sub(start0);
if( 1000000000 < elps ){
fmt.Printf("(repeat#%d %v)\n",ri,elps);
}
}
}
}
func gen(gshPA syscall.ProcAttr, argv []string) {
if len(argv) < 2 {
fmt.Printf("Usage: %s N\n",argv[0])
return
}
// should br repeated by "repeat" command
count, _ := strconv.Atoi(argv[1])
fd := gshPA.Files[1] // Stdout
file := os.NewFile(fd,"internalStdOut")
fmt.Printf("--I-- Gen. Count=%d to [%d]\n",count,file.Fd())
//buf := []byte{}
outdata := "0123 5678 0123 5678 0123 5678 0123 5678\r"
for gi := 0; gi < count; gi++ {
file.WriteString(outdata)
}
//file.WriteString("\n")
fmt.Printf("\n(%d B)\n",count*len(outdata));
//file.Close()
}
// -s, -si, -so // bi-directional, source, sync (maybe socket)
func sconnect(gshCtx GshContext, gshPA syscall.ProcAttr, inTCP bool, argv []string) {
if len(argv) < 2 {
fmt.Printf("Usage: -s [host]:[port[.udp]]\n")
return
}
remote := argv[1]
if remote == ":" { remote = "0.0.0.0:9999" }
if inTCP { // TCP
dport, err := net.ResolveTCPAddr("tcp",remote);
if err != nil {
fmt.Printf("Address error: %s (%s)\n",remote,err)
return
}
conn, err := net.DialTCP("tcp",nil,dport)
if err != nil {
fmt.Printf("Connection error: %s (%s)\n",remote,err)
return
}
file, _ := conn.File();
fd := file.Fd()
fmt.Printf("Socket: connected to %s, socket[%d]\n",remote,fd)
savfd := gshPA.Files[1]
gshPA.Files[1] = fd;
gshellv(gshCtx, gshPA, argv[2:])
gshPA.Files[1] = savfd
file.Close()
conn.Close()
}else{
//dport, err := net.ResolveUDPAddr("udp4",remote);
dport, err := net.ResolveUDPAddr("udp",remote);
if err != nil {
fmt.Printf("Address error: %s (%s)\n",remote,err)
return
}
//conn, err := net.DialUDP("udp4",nil,dport)
conn, err := net.DialUDP("udp",nil,dport)
if err != nil {
fmt.Printf("Connection error: %s (%s)\n",remote,err)
return
}
file, _ := conn.File();
fd := file.Fd()
ar := conn.RemoteAddr()
//al := conn.LocalAddr()
fmt.Printf("Socket: connected to %s [%s], socket[%d]\n",
remote,ar.String(),fd)
savfd := gshPA.Files[1]
gshPA.Files[1] = fd;
gshellv(gshCtx, gshPA, argv[2:])
gshPA.Files[1] = savfd
file.Close()
conn.Close()
}
}
func saccept(gshCtx GshContext, gshPA syscall.ProcAttr, inTCP bool, argv []string) {
if len(argv) < 2 {
fmt.Printf("Usage: -ac [host]:[port[.udp]]\n")
return
}
local := argv[1]
if local == ":" { local = "0.0.0.0:9999" }
if inTCP { // TCP
port, err := net.ResolveTCPAddr("tcp",local);
if err != nil {
fmt.Printf("Address error: %s (%s)\n",local,err)
return
}
//fmt.Printf("Listen at %s...\n",local);
sconn, err := net.ListenTCP("tcp", port)
if err != nil {
fmt.Printf("Listen error: %s (%s)\n",local,err)
return
}
//fmt.Printf("Accepting at %s...\n",local);
aconn, err := sconn.AcceptTCP()
if err != nil {
fmt.Printf("Accept error: %s (%s)\n",local,err)
return
}
file, _ := aconn.File()
fd := file.Fd()
fmt.Printf("Accepted TCP at %s [%d]\n",local,fd)
savfd := gshPA.Files[0]
gshPA.Files[0] = fd;
gshellv(gshCtx, gshPA, argv[2:])
gshPA.Files[0] = savfd
sconn.Close();
aconn.Close();
file.Close();
}else{
//port, err := net.ResolveUDPAddr("udp4",local);
port, err := net.ResolveUDPAddr("udp",local);
if err != nil {
fmt.Printf("Address error: %s (%s)\n",local,err)
return
}
fmt.Printf("Listen UDP at %s...\n",local);
//uconn, err := net.ListenUDP("udp4", port)
uconn, err := net.ListenUDP("udp", port)
if err != nil {
fmt.Printf("Listen error: %s (%s)\n",local,err)
return
}
file, _ := uconn.File()
fd := file.Fd()
ar := uconn.RemoteAddr()
remote := ""
if ar != nil { remote = ar.String() }
if remote == "" { remote = "?" }
// not yet received
//fmt.Printf("Accepted at %s [%d] <- %s\n",local,fd,"")
savfd := gshPA.Files[0]
gshPA.Files[0] = fd;
savenv := gshPA.Env
gshPA.Env = append(savenv, "REMOTE_HOST="+remote)
gshellv(gshCtx, gshPA, argv[2:])
gshPA.Env = savenv
gshPA.Files[0] = savfd
uconn.Close();
file.Close();
}
}
// empty line command
func pwd(gshPA syscall.ProcAttr){
// execute context command, pwd + date
// context notation, representation scheme, to be resumed at re-login
cwd, _ := os.Getwd()
t := time.Now()
date := t.Format(time.UnixDate)
exe, _ := os.Executable()
host, _ := os.Hostname()
fmt.Printf("{PWD=\"%s\"",cwd)
fmt.Printf(" HOST=\"%s\"",host)
fmt.Printf(" DATE=\"%s\"",date)
fmt.Printf(" TIME=\"%s\"",t.String())
fmt.Printf(" PID=\"%d\"",os.Getpid())
fmt.Printf(" EXE=\"%s\"",exe)
fmt.Printf("}\n")
}
// these should be browsed and edited by HTTP browser
// show the time of command with -t and direcotry with -ls
// openfile-history, sort by -a -m -c
// sort by elapsed time by -t -s
// search by "more" like interface
// edit history
// sort history, and wc or uniq
// CPU and other resource consumptions
// limit showing range (by time or so)
// export / import history
func xHistory(gshCtx GshContext, argv []string) (rgshCtx GshContext) {
for i, v := range gshCtx.CommandHistory {
fmt.Printf("!%d ",i)
if isin("-v",argv){
fmt.Println(v) // should be with it date
}else{
if isin("-l",argv){
elps := v.EndAt.Sub(v.StartAt);
start := v.StartAt.Format(time.Stamp)
fmt.Printf("%s (%8v) ",start,elps)
}
fmt.Printf("%s",v.CmdLine)
fmt.Printf("\n")
}
}
return gshCtx
}
// !n - history index
func searchHistory(gshCtx GshContext, gline string) (string, bool, bool){
if gline[0] == '!' {
hix, err := strconv.Atoi(gline[1:])
if err != nil {
fmt.Printf("--E-- (%s : range)\n",hix)
return "", false, true
}
if hix < 0 || len(gshCtx.CommandHistory) <= hix {
fmt.Printf("--E-- (%d : out of range)\n",hix)
return "", false, true
}
return gshCtx.CommandHistory[hix].CmdLine, false, false
}
// search
//for i, v := range gshCtx.CommandHistory {
//}
return gline, false, false
}
// temporary adding to PATH environment
// cd name -lib for LD_LIBRARY_PATH
// chdir with directory history (date + full-path)
// -s for sort option (by visit date or so)
func xChdirHistory(gshCtx GshContext, argv []string){
for i, v := range gshCtx.ChdirHistory {
fmt.Printf("!%d ",i)
fmt.Printf("%v ",v.MovedAt.Format(time.Stamp))
showFileInfo(v.Dir,argv)
}
}
func xChdir(gshCtx GshContext, argv []string) (rgshCtx GshContext) {
cdhist := gshCtx.ChdirHistory
if isin("?",argv ) || isin("-t",argv) {
xChdirHistory(gshCtx,argv)
return gshCtx
}
pwd, _ := os.Getwd()
dir := ""
if len(argv) <= 1 {
dir = toFullpath("~")
}else{
dir = argv[1]
}
if dir[0] == '!' {
if dir == "!0" {
dir = gshCtx.StartDir
}else
if dir == "!!" {
index := len(cdhist) - 1
if 0 < index { index -= 1 }
dir = cdhist[index].Dir
}else{
index, err := strconv.Atoi(dir[1:])
if err != nil {
fmt.Printf("--E-- xChdir(%v)\n",err)
dir = "?"
}else
if len(gshCtx.ChdirHistory) <= index {
fmt.Printf("--E-- xChdir(history range error)\n")
dir = "?"
}else{
dir = cdhist[index].Dir
}
}
}
if dir != "?" {
err := os.Chdir(dir)
if err != nil {
fmt.Printf("--E-- xChdir(%s)(%v)\n",argv[1],err)
}else{
cwd, _ := os.Getwd()
if cwd != pwd {
hist1 := GChdirHistory { }
hist1.Dir = cwd
hist1.MovedAt = time.Now()
gshCtx.ChdirHistory = append(cdhist,hist1)
}
}
}
return gshCtx
}
func showRusage(what string,argv []string, ru *syscall.Rusage){
fmt.Printf("%s: ",what);
fmt.Printf("Usr=%d.%06ds",ru.Utime.Sec,ru.Utime.Usec)
fmt.Printf(" Sys=%d.%06ds",ru.Stime.Sec,ru.Stime.Usec)
fmt.Printf(" Rss=%vB",ru.Maxrss)
if isin("-l",argv) {
fmt.Printf(" MinFlt=%v",ru.Minflt)
fmt.Printf(" MajFlt=%v",ru.Majflt)
fmt.Printf(" IxRSS=%vB",ru.Ixrss)
fmt.Printf(" IdRSS=%vB",ru.Idrss)
fmt.Printf(" Nswap=%vB",ru.Nswap)
fmt.Printf(" Read=%v",ru.Inblock)
fmt.Printf(" Write=%v",ru.Oublock)
}
fmt.Printf(" Snd=%v",ru.Msgsnd)
fmt.Printf(" Rcv=%v",ru.Msgrcv)
//if isin("-l",argv) {
fmt.Printf(" Sig=%v",ru.Nsignals)
//}
fmt.Printf("\n");
}
func xTime(gshCtx GshContext, argv []string) (GshContext,bool) {
if 2 <= len(argv){
xgshCtx, fin := gshellv(gshCtx,gshCtx.gshPA,argv[1:])
gshCtx = xgshCtx
showRusage(argv[1],argv,&gshCtx.LastRusage)
return gshCtx, fin
}else{
rusage:= syscall.Rusage {}
syscall.Getrusage(syscall.RUSAGE_SELF,&rusage)
showRusage("self",argv, &rusage)
syscall.Getrusage(syscall.RUSAGE_CHILDREN,&rusage)
showRusage("child",argv, &rusage)
return gshCtx, false
}
}
func xJobs(gshCtx GshContext, argv []string){
fmt.Printf("%d Jobs\n",len(gshCtx.BackGroundJobs))
for ji, pid := range gshCtx.BackGroundJobs {
//wstat := syscall.WaitStatus {0}
rusage := syscall.Rusage {}
//wpid, err := syscall.Wait4(pid,&wstat,syscall.WNOHANG,&rusage);
wpid, err := syscall.Wait4(pid,nil,syscall.WNOHANG,&rusage);
if err != nil {
fmt.Printf("--E-- %%%d [%d] (%v)\n",ji,pid,err)
}else{
fmt.Printf("%%%d[%d](%d)\n",ji,pid,wpid)
showRusage("chld",argv,&rusage)
}
}
}
func gshellv(gshCtx GshContext, gshPA syscall.ProcAttr, argv []string) (xx GshContext, fin bool) {
//fmt.Printf("--I-- gshellv((%d))\n",len(argv))
if len(argv) <= 0 {
return gshCtx, false
}
for ai := 0; ai < len(argv); ai++ {
argv[ai] = strsubst(argv[ai])
}
if false {
for ai := 0; ai < len(argv); ai++ {
fmt.Printf("[%d] %s [%d]%T\n",
ai,argv[ai],len(argv[ai]),argv[ai])
}
}
cmd := argv[0]
if cmd == "-ot" {
sconnect(gshCtx, gshPA, true, argv)
return gshCtx, false
}
if cmd == "-ou" {
sconnect(gshCtx, gshPA, false, argv)
return gshCtx, false
}
if cmd == "-it" {
saccept(gshCtx, gshPA, true , argv)
return gshCtx, false
}
if cmd == "-iu" {
saccept(gshCtx, gshPA, false, argv)
return gshCtx, false
}
if cmd == "-i" || cmd == "-o" || cmd == "-a" || cmd == "-s" {
if len(argv) < 2 {
return gshCtx, false
}
fdix := 0;
mode := os.O_RDONLY;
if cmd == "-i" {
}
if cmd == "-o" {
fdix = 1;
mode = os.O_RDWR | os.O_CREATE;
}
if cmd == "-a" {
fdix = 1;
mode = os.O_RDWR | os.O_CREATE | os.O_APPEND;
}
f, err := os.OpenFile(argv[1], mode, 0600)
if err != nil {
fmt.Printf("%s\n",err)
return gshCtx, false
}
savfd := gshPA.Files[fdix]
gshPA.Files[fdix] = f.Fd()
fmt.Printf("--I-- Opened [%d] %s\n",f.Fd(),argv[1])
gshCtx, _ = gshellv(gshCtx, gshPA, argv[2:])
gshPA.Files[fdix] = savfd
return gshCtx, false
}
if cmd == "-bg" {
xfin := false
// set background option
gshCtx.BackGround = true
gshCtx, xfin = gshellv(gshCtx,gshPA,argv[1:])
gshCtx.BackGround = false
return gshCtx, xfin
}
if cmd == "call" {
gshCtx, _ = excommand(gshCtx, false,argv[1:])
return gshCtx, false
}
if cmd == "cd" || cmd == "chdir" {
gshCtx = xChdir(gshCtx,argv);
return gshCtx, false
}
if cmd == "#define" {
}
if cmd == "echo" {
echo(argv,true)
return gshCtx, false
}
if cmd == "env" {
env(argv)
return gshCtx, false
}
if cmd == "eval" {
eval(argv,true)
return gshCtx, false
}
if cmd == "exec" {
gshCtx, _ = excommand(gshCtx, true,argv[1:])
// should not return here
return gshCtx, false
}
if cmd == "exit" || cmd == "quit" {
// write Result code EXIT to 3>
return gshCtx, true
}
if cmd == "-find" {
find(argv)
return gshCtx, false
}
if cmd == "fork" {
// mainly for a server
return gshCtx, false
}
if cmd == "-gen" {
gen(gshPA, argv)
return gshCtx, false
}
if cmd == "history" || cmd == "hi" { // hi should be alias
gshCtx = xHistory(gshCtx, argv)
return gshCtx, false
}
if cmd == "jobs" {
xJobs(gshCtx,argv)
return gshCtx, false
}
if cmd == "nop" {
return gshCtx, false
}
if cmd == "pstitle" {
// to be gsh.title
}
if cmd == "repeat" || cmd == "rep" { // repeat cond command
repeat(gshCtx,gshPA,argv)
return gshCtx, false
}
if cmd == "set" { // set name ...
return gshCtx, false
}
if cmd == "time" {
gshCtx, fin = xTime(gshCtx,argv)
return gshCtx, fin
}
if cmd == "sleep" {
sleep(gshCtx,gshPA,argv)
return gshCtx, false
}
if cmd == "-ver" {
fmt.Printf("%s\n",VERSION);
return gshCtx, false
}
if cmd == "pwh" {
pwd(gshPA);
return gshCtx, false
}
if cmd == "where" {
// data file or so?
}
if cmd == "which" {
which("PATH",argv);
return gshCtx, false
}
gshCtx, _ = excommand(gshCtx, false,argv)
return gshCtx, false
}
func gshelll(gshCtx GshContext, gshPA syscall.ProcAttr, gline string) (gx GshContext, rfin bool) {
argv := strings.Split(string(gline)," ")
gshCtx, fin := gshellv(gshCtx,gshPA,argv)
return gshCtx, fin
}
func tgshelll(gshCtx GshContext, gshPA syscall.ProcAttr, gline string) (gx GshContext, xfin bool) {
start := time.Now()
gshCtx, fin := gshelll(gshCtx,gshPA,gline)
end := time.Now()
elps := end.Sub(start);
fmt.Printf("--I-- (%d.%09ds)\n",elps/1000000000,elps%1000000000)
return gshCtx, fin
}
func Ttyid() (int) {
fi, err := os.Stdin.Stat()
if err != nil {
return 0;
}
//fmt.Printf("Stdin: %v Dev=%d\n",
// fi.Mode(),fi.Mode()&os.ModeDevice)
if (fi.Mode() & os.ModeDevice) != 0 {
stat := syscall.Stat_t{};
err := syscall.Fstat(0,&stat)
if err != nil {
//fmt.Printf("--I-- Stdin: (%v)\n",err)
}else{
//fmt.Printf("--I-- Stdin: rdev=%d %d\n",
// stat.Rdev&0xFF,stat.Rdev);
//fmt.Printf("--I-- Stdin: tty%d\n",stat.Rdev&0xFF);
return int(stat.Rdev & 0xFF)
}
}
return 0
}
func ttyfile(gshCtx GshContext) string {
//fmt.Printf("--I-- GSH_HOME=%s\n",gshCtx.GshHomeDir)
ttyfile := gshCtx.GshHomeDir + "/" + "gsh-tty" +
strconv.Itoa(gshCtx.TerminalId)
//fmt.Printf("--I-- ttyfile=%s\n",ttyfile)
return ttyfile
}
func ttyline(gshCtx GshContext) (*os.File){
file, err := os.OpenFile(ttyfile(gshCtx),
os.O_RDWR|os.O_CREATE|os.O_TRUNC,0600)
if err != nil {
fmt.Printf("--F-- cannot open %s (%s)\n",ttyfile(gshCtx),err)
return file;
}
return file
}
func getline(gshCtx GshContext, hix int, skipping, with_exgetline bool, gsh_getlinev[]string, prevline string) (string) {
if( skipping ){
reader := bufio.NewReaderSize(os.Stdin,LINESIZE)
line, _, _ := reader.ReadLine()
return string(line)
}else
if( with_exgetline && gshCtx.GetLine != "" ){
//var xhix int64 = int64(hix); // cast
newenv := os.Environ()
newenv = append(newenv, "GSH_LINENO="+strconv.FormatInt(int64(hix),10) )
tty := ttyline(gshCtx)
tty.WriteString(prevline)
Pa := os.ProcAttr {
"", // start dir
newenv, //os.Environ(),
[]*os.File{os.Stdin,os.Stdout,os.Stderr,tty},
nil,
}
//fmt.Printf("--I-- getline=%s // %s\n",gsh_getlinev[0],gshCtx.GetLine)
proc, err := os.StartProcess(gsh_getlinev[0],[]string{"getline","getline"},&Pa)
if err != nil {
fmt.Printf("Proc ERROR (%s)\n",nil)
for ; ; {
}
}
//stat, err := proc.Wait()
proc.Wait()
buff := make([]byte,LINESIZE)
count, err := tty.Read(buff)
//_, err = tty.Read(buff)
//fmt.Printf("--D-- getline (%d)\n",count)
if err != nil {
if ! (count == 0) { // && err.String() == "EOF" ) {
fmt.Printf("--E-- getline error (%s)\n",err)
}
}else{
//fmt.Printf("--I-- getline OK \"%s\"\n",buff)
}
tty.Close()
return string(buff[0:count])
}else{
// if isatty {
fmt.Printf("!%d",hix)
fmt.Print(PROMPT)
// }
reader := bufio.NewReaderSize(os.Stdin,LINESIZE)
line, _, _ := reader.ReadLine()
return string(line)
}
}
//
// $USERHOME/.gsh/
// gsh-history.txt
// gsh-aliases.txt // should be conditional?
//
func gshSetup(gshCtx GshContext) (GshContext, bool) {
homedir, err := os.UserHomeDir()
if err != nil {
fmt.Printf("--E-- You have no UserHomeDir (%v)\n",err)
return gshCtx, true
}
gshhome := homedir + "/" + GSH_HOME
_, err2 := os.Stat(gshhome)
if err2 != nil {
err3 := os.Mkdir(gshhome,0700)
if err3 != nil {
fmt.Printf("--E-- Could not Create %s (%s)\n",
gshhome,err)
return gshCtx, true
}
fmt.Printf("--I-- Created %s\n",gshhome)
}
gshCtx.GshHomeDir = gshhome
return gshCtx, false
}
func script(gshCtx GshContext) (_ GshContext) {
gshCtx, err0 := gshSetup(gshCtx)
if err0 {
return gshCtx
}
gshPA := syscall.ProcAttr {
"", // the staring directory
os.Environ(), // environ[]
[]uintptr{os.Stdin.Fd(),os.Stdout.Fd(),os.Stderr.Fd()},
nil, // OS specific
}
cwd, _ := os.Getwd()
gshCtx = GshContext {
cwd, // StartDir
"", // GetLine
[]GChdirHistory { {cwd,time.Now()} }, // ChdirHistory
gshPA,
[]GCommandHistory{ }, //something for invokation?
false,
[]int{},
syscall.Rusage{},
"", // GshHomeDir
Ttyid(),
}
gshCtx, _ = gshSetup(gshCtx)
fmt.Printf("--I-- GSH_HOME=%s\n",gshCtx.GshHomeDir)
//resmap()
gsh_getlinev, with_exgetline :=
which("PATH",[]string{"which","gsh-getline","-s"})
if with_exgetline {
gsh_getlinev[0] = toFullpath(gsh_getlinev[0])
gshCtx.GetLine = toFullpath(gsh_getlinev[0])
}else{
fmt.Printf("--W-- No gsh-getline found. Using internal getline.\n");
}
prevline := ""
skipping := false
for hix := 1; ; {
gline := getline(gshCtx,hix,skipping,with_exgetline,gsh_getlinev,prevline)
if skipping {
if strings.Index(gline,"fi") == 0 {
fmt.Printf("fi\n");
skipping = false;
}else{
//fmt.Printf("%s\n",gline);
}
continue
}
if strings.Index(gline,"if") == 0 {
//fmt.Printf("--D-- if start: %s\n",gline);
skipping = true;
continue
}
if 0 < len(gline) && gline[0] == '!' {
xgline, set, err := searchHistory(gshCtx,gline)
if err {
continue
}
if set {
// set the line in command line editor
}
gline = xgline
}
ghist := GCommandHistory { }
ghist.StartAt = time.Now()
xgshCtx, fin := tgshelll(gshCtx,gshPA,gline)
gshCtx = xgshCtx
ghist.EndAt = time.Now()
ghist.CmdLine = gline
gshCtx.CommandHistory = append(gshCtx.CommandHistory, ghist)
if fin {
break;
}
if len(gline) == 0 {
pwd(gshPA);
continue;
}
prevline = gline;
hix++;
}
return gshCtx
}
func main() {
gshPA := syscall.ProcAttr {
"", // the staring directory
os.Environ(), // environ[]
[]uintptr{},
nil, // OS specific
}
gshCtx := GshContext {
"", // StartDir
"", // GetLine
[]GChdirHistory{ {"",time.Now()} },
gshPA,
[]GCommandHistory{ },
false,
[]int{},
syscall.Rusage{},
"", // GshHomeDir
Ttyid(),
}
gshCtx = script(gshCtx)
fmt.Printf("%s\n",gshCtx.StartDir)
}
// TODO:
// - inter gsh communication, possibly running in remote hosts -- to be remote shell
// - merged histories of multiple parallel gsh sessions
// - alias as a function
// - instant alias end environ export to the permanent > ~/.gsh/gsh-alias and gsh-environ
// - retrieval PATH of files by its type
// - gsh as an IME
// - all commands have its subucomand after "---" symbol
// - filename expansion by "-find" command
// - history of ext code and output of each commoand
//---END--- (^-^)/