├── .gitignore ├── LICENSE ├── README.md ├── base32 ├── README.md ├── base32.go └── base32 │ └── base32.go ├── base64 ├── README.md ├── base64.go └── base64 │ └── base64.go ├── basecore ├── basecore.go └── basecore_test.go ├── basename ├── README.md ├── basename.go └── basename │ └── basename.go ├── buildall └── buildall.go ├── cat ├── README.md ├── cat.go ├── cat │ └── cat.go └── cat_test.go ├── chgrp ├── README.md ├── chgrp.go ├── chgrp │ └── chgrp.go ├── chgrp_test.go └── test.dat ├── chmod └── chmod.go ├── chown ├── README.md ├── chown.go ├── chown │ └── chown.go ├── chown_test.go └── test.dat ├── coreutils └── coreutils.go ├── cut ├── README.md ├── cut.go ├── cut │ └── cut.go └── cut_test.go ├── dirname ├── README.md ├── dirname.go └── dirname │ └── dirname.go ├── echo ├── README.md ├── echo.go ├── echo │ └── echo.go └── echo_test.go ├── env └── env.go ├── hashcore ├── hashcore.go ├── hashcore_test.go ├── md5sum.sum ├── md5sum2.sum ├── ok.binary.md5.sum └── ok.md5.sum ├── head ├── README.md ├── head.go ├── head │ └── head.go └── head_test.go ├── join ├── README.md ├── join.go └── join │ └── main.go ├── link └── link.go ├── md5sum ├── README.md ├── md5sum.go └── md5sum │ └── md5sum.go ├── mv ├── README.md └── mv.go ├── paste ├── README.md ├── paste.go ├── paste │ └── paste.go └── session_id.log ├── pwd ├── README.md ├── pwd.go └── pwd │ └── pwd.go ├── realpath └── realpath.go ├── rmdir ├── README.md ├── rmdir.go └── rmdir │ └── rmdir.go ├── runtest └── runtest.go ├── seq ├── README.md ├── seq.go ├── seq │ └── seq.go └── seq_test.go ├── sha1sum ├── README.md ├── sha1sum.go └── sha1sum │ └── sha1sum.go ├── sha224sum ├── README.md ├── sha224sum.go └── sha224sum │ └── sha224sum.go ├── sha256sum ├── README.md ├── sha256sum.go └── sha256sum │ └── sha256sum.go ├── sha384sum ├── README.md ├── sha384sum.go └── sha384sum │ └── sha384sum.go ├── sha512sum ├── README.md ├── sha512sum.go └── sha512sum │ └── sha512sum.go ├── shuf ├── README.md ├── shuf.go └── shuf │ └── shuf.go ├── sleep ├── README.md ├── sleep.go └── sleep │ └── sleep.go ├── sort ├── README.md └── sort.go ├── split ├── README.md └── split.go ├── tac ├── README.md ├── tac.go ├── tac │ └── tac.go └── tac_test.go ├── tail ├── README.md ├── tail.go ├── tail │ └── tail.go └── tail_test.go ├── tee ├── README.md ├── tee.go ├── tee │ └── tee.go ├── tee_darwin.go └── tee_linux.go ├── touch ├── README.md ├── hello.txt ├── touch.go ├── touch │ └── touch.go └── touch_test.go ├── tr ├── README.md ├── tr.go ├── tr │ └── tr.go └── tr_test.go ├── true ├── README.md ├── true.go └── true │ └── true.go ├── uname ├── README.md ├── uname.go └── uname │ └── uname.go ├── uniq ├── README.md ├── uniq.go └── uniq │ └── uniq.go ├── unlink ├── unlink.go └── unlink │ └── unlink.go ├── utils ├── args.go ├── parse_size.go ├── parse_size_test.go ├── type.go ├── utils.go └── walk.go ├── wc ├── wc.go └── wc │ └── wc.go ├── whoami ├── README.md ├── whoami.go └── whoami │ └── whomai.go └── yes ├── README.md ├── yes.go └── yes └── yes.go /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *~ 3 | *swp 4 | *.exe 5 | *.exe~ 6 | *.dll 7 | *.so 8 | *.dylib 9 | 10 | # Test binary, build with `go test -c` 11 | *.test 12 | 13 | # Output of the go coverage tool, specifically when used with LiteIDE 14 | *.out 15 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # coreutils 2 | 3 | ## summary 4 | The coreutils project is a reimplemented version of golang 5 | 6 | ## install coreutils 7 | ``` 8 | env GOPATH=`pwd` go get github.com/guonaihong/coreutils/coreutils 9 | ``` 10 | If you want to use the cat command 11 | ``` 12 | ./coreutils cat flie 13 | ./coreutils cut -d":" -f1 /etc/passwd 14 | ./coreutils echo "hello china" 15 | ``` 16 | 17 | ## install Compile command separately 18 | ``` 19 | env GOPATH=`pwd` go run github.com/guonaihong/coreutils/buildall 20 | ``` 21 | If you want to use the cat command 22 | ``` 23 | ./cat flie 24 | ./cut -d":" -f1 /etc/passwd 25 | ./echo "hello china" 26 | ``` 27 | 28 | ## The completed command is as follows 29 | * base32 [detail](./base32/README.md) 30 | * base64 [detail](./base64/README.md) 31 | * basename [detail](./basename/README.md) 32 | * cat [detail](./cat/README.md) 33 | * chgrp [detail](./chgrp/README.md) 34 | * chown [detail](./chown/README.md) 35 | * cut [detail](./cut/README.md) 36 | * dirname [detail](./dirname/README.md) 37 | * echo [detail](./echo/README.md) 38 | * head [detail](./head/README.md) 39 | * env 40 | * link 41 | * md5sum [detail](./md5sum/README.md) 42 | * paste [detail](./paste/README.md) 43 | * pwd [detail](./pwd/README.md) 44 | * rmdir [detail](./rmdir/README.md) 45 | * tee [detail](./tee/README.md) 46 | * touch [detail](./touch/README.md) 47 | * tail [detail](./tail/README.md) 48 | * tac [detail](./tac/README.md) 49 | * tr [detail](./tr/README.md) 50 | * true [detail](./true/README.md) 51 | * uname [detail](./uname/README.md) 52 | * uniq [detail](./uniq/README.md) 53 | * unlink 54 | * whoami [detail](./whoami/README.md) 55 | * yes [detail](./yes/README.md) 56 | * shuf [detail](./shuf/README.md) 57 | * seq [detail](./seq/README.md) 58 | * sha1sum [detail](./sha1sum/README.md) 59 | * sha224sum [detail](./sha224sum/README.md) 60 | * sha256sum [detail](./sha256sum/README.md) 61 | * sha384sum [detail](./sha384sum/README.md) 62 | * sha512sum [detail](./sha512/README.md) 63 | * sleep [detail](./sleep/README.md) 64 | 65 | ## progress 66 | progress = 34 / 92 = 36.7% 67 | -------------------------------------------------------------------------------- /base32/README.md: -------------------------------------------------------------------------------- 1 | # base32 2 | 3 | #### summary 4 | base32 has the same base32 command as ubuntu 18.04 5 | 6 | #### install 7 | ``` 8 | env GOPATH=`pwd` go get -u github.com/guonaihong/coreutils/base32/base32 9 | ``` 10 | 11 | #### usage 12 | ```console 13 | Usage of base32: 14 | -d, --decode 15 | decode data 16 | -i, --ignore-garbage 17 | when decoding, ignore non-alphabet characters 18 | -w, --wrap int 19 | wrap encoded lines after COLS character (default 76). 20 | Use 0 to disable line wrapping 21 | ``` 22 | -------------------------------------------------------------------------------- /base32/base32.go: -------------------------------------------------------------------------------- 1 | package base32 2 | 3 | import ( 4 | "github.com/guonaihong/coreutils/basecore" 5 | ) 6 | 7 | func Main(argv []string) { 8 | basecore.Main(argv, "base32") 9 | } 10 | -------------------------------------------------------------------------------- /base32/base32/base32.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/guonaihong/coreutils/base32" 5 | "os" 6 | ) 7 | 8 | func main() { 9 | base32.Main(os.Args) 10 | } 11 | -------------------------------------------------------------------------------- /base64/README.md: -------------------------------------------------------------------------------- 1 | # base64 2 | 3 | #### summary 4 | base64 has the same base64 command as ubuntu 18.04 5 | 6 | #### install 7 | ``` 8 | env GOPATH=`pwd` go get -u github.com/guonaihong/coreutils/base64/base64 9 | ``` 10 | 11 | #### usage 12 | ```console 13 | Usage of base64: 14 | -d, --decode 15 | decode data 16 | -i, --ignore-garbage 17 | when decoding, ignore non-alphabet characters 18 | -w, --wrap int 19 | wrap encoded lines after COLS character (default 76). 20 | Use 0 to disable line wrapping 21 | ``` 22 | -------------------------------------------------------------------------------- /base64/base64.go: -------------------------------------------------------------------------------- 1 | package base64 2 | 3 | import ( 4 | "github.com/guonaihong/coreutils/basecore" 5 | ) 6 | 7 | func Main(argv []string) { 8 | basecore.Main(argv, "base64") 9 | } 10 | -------------------------------------------------------------------------------- /base64/base64/base64.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/guonaihong/coreutils/base64" 5 | "os" 6 | ) 7 | 8 | func main() { 9 | base64.Main(os.Args) 10 | } 11 | -------------------------------------------------------------------------------- /basecore/basecore.go: -------------------------------------------------------------------------------- 1 | package basecore 2 | 3 | import ( 4 | "bytes" 5 | "encoding/base32" 6 | "encoding/base64" 7 | "fmt" 8 | "github.com/guonaihong/coreutils/utils" 9 | "github.com/guonaihong/flag" 10 | "io" 11 | "os" 12 | ) 13 | 14 | type Base struct { 15 | OpenDecode *bool 16 | IgnoreGarbage *bool 17 | Wrap *int 18 | rs io.ReadSeeker 19 | w io.Writer 20 | buffer bytes.Buffer 21 | buf []byte 22 | encodeFlush bool 23 | needChar func(b byte) bool 24 | baseName string 25 | } 26 | 27 | func New(argv []string) (*Base, []string) { 28 | b := Base{} 29 | command := flag.NewFlagSet(argv[0], flag.ExitOnError) 30 | 31 | b.OpenDecode = command.Opt("d, decode", "decode data"). 32 | Flags(flag.PosixShort).NewBool(false) 33 | 34 | b.IgnoreGarbage = command.Opt("i, ignore-garbage", 35 | "when decoding, ignore non-alphabet characters"). 36 | Flags(flag.PosixShort).NewBool(false) 37 | 38 | b.Wrap = command.Opt("w, wrap", "wrap encoded lines after COLS character (default 76).\n"+ 39 | "Use 0 to disable line wrapping"). 40 | Flags(flag.PosixShort).NewInt(76) 41 | 42 | command.Parse(argv[1:]) 43 | args := command.Args() 44 | 45 | return &b, args 46 | } 47 | 48 | func (b *Base) IsWrap() bool { 49 | return !(b.Wrap == nil || *b.Wrap == 0) 50 | } 51 | 52 | func (b *Base) Write(p []byte) (n int, err error) { 53 | 54 | if !b.IsWrap() { 55 | return b.w.Write(p) 56 | } 57 | 58 | b.buffer.Write(p) 59 | for b.buffer.Len() >= len(b.buf) { 60 | n1, err := b.buffer.Read(b.buf) 61 | if err != nil { 62 | fmt.Printf("read fail:%s\n", err) 63 | break 64 | } 65 | 66 | b.w.Write(b.buf[:n1]) 67 | b.w.Write([]byte{'\n'}) 68 | } 69 | 70 | if b.encodeFlush && b.buffer.Len() > 0 { 71 | defer func() { 72 | b.buffer.Reset() 73 | b.encodeFlush = false 74 | b.w.Write([]byte{'\n'}) 75 | }() 76 | 77 | return b.w.Write(b.buffer.Bytes()) 78 | } 79 | 80 | return 81 | } 82 | 83 | // todo < 0 84 | func (b *Base) checkArgs() error { 85 | if b.Wrap != nil && *b.Wrap < 0 { 86 | return fmt.Errorf(`%s: invalid wrap size: "%d"`, b.baseName, *b.Wrap) 87 | } 88 | return nil 89 | } 90 | 91 | func (b *Base) Encode(rs io.ReadSeeker, w io.Writer) { 92 | b.w = w 93 | 94 | var encoder io.WriteCloser 95 | if b.baseName == "base32" { 96 | encoder = base32.NewEncoder(base32.StdEncoding, b) 97 | } else { 98 | encoder = base64.NewEncoder(base64.StdEncoding, b) 99 | } 100 | 101 | io.Copy(encoder, rs) 102 | 103 | b.encodeFlush = true 104 | encoder.Close() 105 | b.Write([]byte{}) //encodeFlush 106 | 107 | if !b.IsWrap() { 108 | w.Write([]byte{'\n'}) 109 | } 110 | } 111 | 112 | func (b *Base) Read(p []byte) (n int, err error) { 113 | if b.IgnoreGarbage != nil && *b.IgnoreGarbage { 114 | if b.buf == nil || len(p) > len(b.buf) { 115 | b.buf = make([]byte, len(p)) 116 | } 117 | 118 | n1, err := b.rs.Read(b.buf) 119 | if err != nil { 120 | return n, err 121 | } 122 | 123 | for _, v := range b.buf[:n1] { 124 | if !b.needChar(v) { 125 | continue 126 | } 127 | 128 | p[n] = v 129 | n++ 130 | } 131 | 132 | copy(p, b.buf[:n+1]) 133 | return n, nil 134 | } 135 | 136 | return b.rs.Read(p) 137 | } 138 | 139 | func (b *Base) Decode(rs io.ReadSeeker, w io.Writer) { 140 | b.rs = rs 141 | // base32 142 | // ABCDEFGHIJKLMNOPQRSTUVWXYZ234567= 143 | b.needChar = func(b byte) bool { 144 | return b >= 'A' && b <= 'Z' || 145 | b >= '2' && b <= '7' || 146 | b == '=' 147 | } 148 | 149 | if b.baseName == "base64" { 150 | // base64 151 | // abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789=+/ 152 | b.needChar = func(b byte) bool { 153 | return b >= 'a' && b <= 'z' || 154 | b >= 'A' && b <= 'Z' || 155 | b >= '0' && b <= '9' || 156 | b == '=' || 157 | b == '+' || 158 | b == '/' 159 | } 160 | } 161 | 162 | var decode io.Reader 163 | 164 | if b.baseName == "base32" { 165 | decode = base32.NewDecoder(base32.StdEncoding, b) 166 | } else { 167 | decode = base64.NewDecoder(base64.StdEncoding, b) 168 | } 169 | 170 | io.Copy(w, decode) 171 | 172 | } 173 | 174 | func (b *Base) Base(rs io.ReadSeeker, w io.Writer) { 175 | if b.IsWrap() { 176 | b.buf = make([]byte, *b.Wrap) 177 | } 178 | 179 | if b.OpenDecode != nil && *b.OpenDecode { 180 | b.Decode(rs, w) 181 | return 182 | } 183 | 184 | b.Encode(rs, w) 185 | } 186 | 187 | func Main(argv []string, baseName string) { 188 | b, args := New(argv) 189 | if len(args) == 0 { 190 | args = append(args, "-") 191 | } 192 | 193 | b.baseName = baseName 194 | err := b.checkArgs() 195 | if err != nil { 196 | utils.Die("%s\n", err) 197 | } 198 | 199 | for _, fileName := range args { 200 | f, err := utils.OpenFile(fileName) 201 | if err != nil { 202 | utils.Die("base32: %s\n", err) 203 | } 204 | 205 | b.Base(f, os.Stdout) 206 | f.Close() 207 | } 208 | } 209 | -------------------------------------------------------------------------------- /basecore/basecore_test.go: -------------------------------------------------------------------------------- 1 | package basecore 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "github.com/guonaihong/coreutils/utils" 7 | "strings" 8 | "testing" 9 | ) 10 | 11 | func testEncode64(src, dst string, wrap *int, t *testing.T) { 12 | w := &bytes.Buffer{} 13 | 14 | b := Base{} 15 | b.baseName = "base64" 16 | b.Wrap = wrap 17 | 18 | b.Base(strings.NewReader(src), w) 19 | 20 | if w.String() != dst { 21 | var wrapMessage string 22 | if wrap == nil { 23 | wrapMessage = "wrap is nil" 24 | } else { 25 | wrapMessage = fmt.Sprintf("wrap = %d", *wrap) 26 | } 27 | 28 | t.Errorf("base64 fail(%s), need(%s), %s\n", w.String(), dst, wrapMessage) 29 | } 30 | } 31 | 32 | func TestEncode64(t *testing.T) { 33 | src := "abcdefghijklmnopqrstuvwxyz" 34 | dst := "YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXo=\n" 35 | testEncode64(src, dst, nil, t) 36 | testEncode64(src, dst, utils.Int(0), t) 37 | testEncode64(src, dst, utils.Int(76), t) 38 | 39 | src = "hello china" 40 | dst = "aGVsbG8gY2hpbmE=\n" 41 | testEncode64(src, dst, nil, t) 42 | testEncode64(src, dst, utils.Int(0), t) 43 | testEncode64(src, dst, utils.Int(76), t) 44 | 45 | src = "hello china\n" 46 | dst = "aGVsbG8gY2hpbmEK\n" 47 | testEncode64(src, dst, nil, t) 48 | testEncode64(src, dst, utils.Int(0), t) 49 | testEncode64(src, dst, utils.Int(76), t) 50 | } 51 | 52 | func testEncode32(src, dst string, wrap *int, t *testing.T) { 53 | w := &bytes.Buffer{} 54 | 55 | b := Base{} 56 | b.baseName = "base32" 57 | 58 | b.Base(strings.NewReader(src), w) 59 | 60 | if w.String() != dst { 61 | var wrapMessage string 62 | if wrap == nil { 63 | wrapMessage = "wrap is nil" 64 | } else { 65 | wrapMessage = fmt.Sprintf("wrap = %d", *wrap) 66 | } 67 | 68 | t.Errorf("base32 fail(%s), need(%s), src(%s) wrap(%s)\n", w.String(), dst, src, wrapMessage) 69 | } 70 | } 71 | 72 | func TestEncode32(t *testing.T) { 73 | src := "abcdefghijklmnopqrstuvwxyz" 74 | dst := "MFRGGZDFMZTWQ2LKNNWG23TPOBYXE43UOV3HO6DZPI======\n" 75 | testEncode32(src, dst, nil, t) 76 | testEncode32(src, dst, utils.Int(0), t) 77 | testEncode32(src, dst, utils.Int(76), t) 78 | 79 | src = "hello china\n" 80 | dst = "NBSWY3DPEBRWQ2LOMEFA====\n" 81 | testEncode32(src, dst, nil, t) 82 | testEncode32(src, dst, utils.Int(0), t) 83 | testEncode32(src, dst, utils.Int(76), t) 84 | 85 | src = "hello china" 86 | dst = "NBSWY3DPEBRWQ2LOME======\n" 87 | testEncode32(src, dst, nil, t) 88 | testEncode32(src, dst, utils.Int(0), t) 89 | testEncode32(src, dst, utils.Int(76), t) 90 | 91 | src = "hello\n" 92 | dst = "NBSWY3DPBI======\n" 93 | testEncode32(src, dst, nil, t) 94 | testEncode32(src, dst, utils.Int(0), t) 95 | testEncode32(src, dst, utils.Int(76), t) 96 | } 97 | 98 | func testDecode32(src, dst string, t *testing.T) { 99 | w := &bytes.Buffer{} 100 | 101 | b := Base{} 102 | b.baseName = "base32" 103 | b.OpenDecode = utils.Bool(true) 104 | 105 | b.Base(strings.NewReader(src), w) 106 | 107 | if w.String() != dst { 108 | t.Errorf("base32 -d fail(%s), need(%s)\n", w.String(), dst) 109 | } 110 | } 111 | 112 | func TestDecode32(t *testing.T) { 113 | src := "MFRGGZDFMZTWQ2LKNNWG23TPOBYXE43UOV3HO6DZPI======\n" 114 | dst := "abcdefghijklmnopqrstuvwxyz" 115 | testDecode32(src, dst, t) 116 | 117 | src = "NBSWY3DPEBRWQ2LOMEFA====\n" 118 | dst = "hello china\n" 119 | testDecode32(src, dst, t) 120 | 121 | src = "NBSWY3DPEBRWQ2LOME======\n" 122 | dst = "hello china" 123 | testDecode32(src, dst, t) 124 | } 125 | 126 | func testDecode64(src, dst string, t *testing.T) { 127 | w := &bytes.Buffer{} 128 | 129 | b := Base{} 130 | b.baseName = "base64" 131 | b.OpenDecode = utils.Bool(true) 132 | 133 | b.Base(strings.NewReader(src), w) 134 | 135 | if w.String() != dst { 136 | t.Errorf("base64 -d fail(%s), need(%s)\n", w.String(), dst) 137 | } 138 | } 139 | 140 | func TestDecode64(t *testing.T) { 141 | src := "YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXo=\n" 142 | dst := "abcdefghijklmnopqrstuvwxyz" 143 | testDecode64(src, dst, t) 144 | 145 | dst = "hello china" 146 | src = "aGVsbG8gY2hpbmE=\n" 147 | testDecode64(src, dst, t) 148 | 149 | dst = "hello china\n" 150 | src = "aGVsbG8gY2hpbmEK\n" 151 | testDecode64(src, dst, t) 152 | } 153 | 154 | func testDecode32IgnoreGarbage(src, dst string, t *testing.T) { 155 | w := &bytes.Buffer{} 156 | 157 | b := Base{} 158 | b.baseName = "base32" 159 | b.OpenDecode = utils.Bool(true) 160 | b.IgnoreGarbage = utils.Bool(true) 161 | 162 | b.Base(strings.NewReader(src), w) 163 | 164 | if w.String() != dst { 165 | t.Errorf("base32 -d fail(%s), need(%s)\n", w.String(), dst) 166 | } 167 | } 168 | 169 | func TestDecode32IgnoreGarbage(t *testing.T) { 170 | src := "MFRGGZDFMZTWQ2LKNNWG23TPOBYXE43UOV3HO6DZPI======\n" 171 | dst := "abcdefghijklmnopqrstuvwxyz" 172 | testDecode32IgnoreGarbage(src, dst, t) 173 | 174 | src = "NBSWY3DPEBRWQ2LOMEFA====\n" 175 | dst = "hello china\n" 176 | testDecode32IgnoreGarbage(src, dst, t) 177 | 178 | src = "NBSWY3DPEBRWQ2LOME======\n" 179 | dst = "hello china" 180 | testDecode32IgnoreGarbage(src, dst, t) 181 | } 182 | 183 | func testDecode64IgnoreGarbage(src, dst string, t *testing.T) { 184 | w := &bytes.Buffer{} 185 | 186 | b := Base{} 187 | b.baseName = "base64" 188 | b.OpenDecode = utils.Bool(true) 189 | b.IgnoreGarbage = utils.Bool(true) 190 | 191 | b.Base(strings.NewReader(src), w) 192 | 193 | if w.String() != dst { 194 | t.Errorf("base64 -d fail(%s), need(%s)\n", w.String(), dst) 195 | } 196 | } 197 | 198 | func TestDecode64IgnoreGarbage(t *testing.T) { 199 | src := "YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXo=\n" 200 | dst := "abcdefghijklmnopqrstuvwxyz" 201 | testDecode64IgnoreGarbage(src, dst, t) 202 | 203 | dst = "hello china" 204 | src = "aGVsbG8gY2hpbmE=\n" 205 | testDecode64IgnoreGarbage(src, dst, t) 206 | 207 | dst = "hello china\n" 208 | src = "aGVsbG8gY2hpbmEK\n" 209 | testDecode64IgnoreGarbage(src, dst, t) 210 | } 211 | -------------------------------------------------------------------------------- /basename/README.md: -------------------------------------------------------------------------------- 1 | # basename 2 | 3 | #### summary 4 | basename 5 | 6 | #### install 7 | ```bash 8 | env GOPATH=`pwd` go get -u github.com/guonaihong/basename/basename 9 | ``` 10 | 11 | #### usage 12 | ```console 13 | Usage of basename: 14 | -a, --multiple 15 | support multiple arguments and treat each as a NAME 16 | -s, --suffix string 17 | remove a trailing SUFFIX; implies -a 18 | -z, --zero 19 | end each output line with NUL, not newline 20 | ``` 21 | -------------------------------------------------------------------------------- /basename/basename.go: -------------------------------------------------------------------------------- 1 | package basename 2 | 3 | import ( 4 | "bytes" 5 | "github.com/guonaihong/flag" 6 | "os" 7 | "path/filepath" 8 | "strings" 9 | ) 10 | 11 | func Main(argv []string) { 12 | command := flag.NewFlagSet(argv[0], flag.ExitOnError) 13 | 14 | multiple := command.Opt("a, multiple", "support multiple arguments and treat each as a NAME"). 15 | Flags(flag.PosixShort).NewBool(false) 16 | 17 | suffix := command.Opt("s, suffix", "remove a trailing SUFFIX; implies -a"). 18 | NewString("") 19 | 20 | zero := command.Opt("z, zero", "end each output line with NUL, not newline"). 21 | Flags(flag.PosixShort).NewBool(false) 22 | 23 | command.Parse(argv[1:]) 24 | 25 | args := command.Args() 26 | 27 | var out bytes.Buffer 28 | 29 | baseNameCore := func(baseName string) { 30 | if strings.HasSuffix(baseName, *suffix) { 31 | baseName = baseName[:len(baseName)-len(*suffix)] 32 | } 33 | 34 | out.WriteString(baseName) 35 | if *zero { 36 | out.WriteByte(0) 37 | } else { 38 | out.WriteByte('\n') 39 | } 40 | } 41 | 42 | if *multiple { 43 | for _, a := range args { 44 | baseName := filepath.Base(a) 45 | 46 | baseNameCore(baseName) 47 | } 48 | } 49 | 50 | if out.Len() == 0 && len(args) > 0 { 51 | baseName := filepath.Base(args[0]) 52 | 53 | if len(args) == 2 { 54 | *suffix = args[1] 55 | } 56 | 57 | baseNameCore(baseName) 58 | } 59 | 60 | os.Stdout.Write(out.Bytes()) 61 | } 62 | -------------------------------------------------------------------------------- /basename/basename/basename.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/guonaihong/coreutils/basename" 5 | "os" 6 | ) 7 | 8 | func main() { 9 | basename.Main(os.Args) 10 | } 11 | -------------------------------------------------------------------------------- /buildall/buildall.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os/exec" 6 | ) 7 | 8 | func main() { 9 | command := []string{ 10 | "base32", 11 | "base64", 12 | "basename", 13 | "cat", 14 | "cut", 15 | "dirname", 16 | "echo", 17 | "head", 18 | "paste", 19 | "pwd", 20 | "tee", 21 | "tail", 22 | "tr", 23 | "true", 24 | "uname", 25 | "uniq", 26 | "unlink", 27 | "whoami", 28 | "yes", 29 | "sleep", 30 | "tac", 31 | } 32 | 33 | for _, c := range command { 34 | runCmd := fmt.Sprintf("env GOPATH=`pwd` go build github.com/guonaihong/coreutils/%s/%s", c, c) 35 | 36 | fmt.Printf("%s\n", runCmd) 37 | cmd := exec.Command("bash", "-c", runCmd) 38 | 39 | out, err := cmd.Output() 40 | if err != nil { 41 | fmt.Println(err) 42 | } 43 | 44 | fmt.Println(string(out)) 45 | } 46 | 47 | } 48 | -------------------------------------------------------------------------------- /cat/README.md: -------------------------------------------------------------------------------- 1 | # cat 2 | 3 | #### summary 4 | cat has the same cat command as ubuntu 18.04 5 | 6 | #### install 7 | ``` 8 | env GOPATH=`pwd` go get -u github.com/guonaihong/coreutils/cat/cat 9 | ``` 10 | 11 | #### usage 12 | ```console 13 | Usage of cat: 14 | -A, --show-all 15 | equivalent to -vET 16 | -E, --show-end 17 | display $ at end of each line 18 | -T, --show-tabs 19 | display TAB characters as ^I 20 | -b, --number-nonblank 21 | number nonempty output lines, overrides -n 22 | -e equivalent to -vE 23 | -n, --numbe 24 | number all output line 25 | -s, --squeeze-blank 26 | suppress repeated empty output lines 27 | -t equivalent to -vT 28 | -v, --show-nonprinting 29 | use ^ and M- notation, except for LFD and TAB 30 | ``` 31 | -------------------------------------------------------------------------------- /cat/cat.go: -------------------------------------------------------------------------------- 1 | package cat 2 | 3 | import ( 4 | "bufio" 5 | "bytes" 6 | "fmt" 7 | "github.com/guonaihong/coreutils/utils" 8 | "github.com/guonaihong/flag" 9 | "io" 10 | "os" 11 | "strings" 12 | ) 13 | 14 | type Cat struct { 15 | NumberNonblank bool 16 | ShowEnds bool 17 | Number bool 18 | SqueezeBlank bool 19 | ShowTabs bool 20 | ShowNonprinting bool 21 | oldNew []string 22 | } 23 | 24 | func writeNonblank(l []byte) []byte { 25 | var out bytes.Buffer 26 | 27 | for _, c := range l { 28 | switch { 29 | case c == 9: // '\t' 30 | out.WriteByte(c) 31 | case c >= 0 && c <= 8 || c > 10 && c <= 31: 32 | out.Write([]byte{'^', c + 64}) 33 | case c >= 32 && c <= 126 || c == 10: // 10 is '\n' 34 | out.WriteByte(c) 35 | case c == 127: 36 | out.Write([]byte{'^', c - 64}) 37 | case c >= 128 && c <= 159: 38 | out.Write([]byte{'M', '-', '^', c - 64}) 39 | case c >= 160 && c <= 254: 40 | out.Write([]byte{'M', '-', c - 128}) 41 | default: 42 | out.Write([]byte{'M', '-', '^', 63}) 43 | } 44 | } 45 | 46 | return out.Bytes() 47 | } 48 | 49 | func New(argv []string) (*Cat, []string) { 50 | c := Cat{} 51 | command := flag.NewFlagSet(argv[0], flag.ExitOnError) 52 | 53 | showAll := command.Opt("A, show-all", "equivalent to -vET"). 54 | Flags(flag.PosixShort).NewBool(false) 55 | 56 | command.Opt("b, number-nonblank", 57 | "number nonempty output lines, overrides -n"). 58 | Flags(flag.PosixShort).Var(&c.NumberNonblank) 59 | 60 | e := command.Opt("e", "equivalent to -vE"). 61 | Flags(flag.PosixShort).NewBool(false) 62 | 63 | command.Opt("E, show-end", "display $ at end of each line"). 64 | Flags(flag.PosixShort).Var(&c.ShowEnds) 65 | 66 | command.Opt("n, number", "number all output line"). 67 | Flags(flag.PosixShort).Var(&c.Number) 68 | 69 | command.Opt("s, squeeze-blank", 70 | "suppress repeated empty output lines"). 71 | Flags(flag.PosixShort).Var(&c.SqueezeBlank) 72 | 73 | t := command.Opt("t", "equivalent to -vT"). 74 | Flags(flag.PosixShort).NewBool(false) 75 | 76 | command.Opt("T, show-tabs", "display TAB characters as ^I"). 77 | Flags(flag.PosixShort).Var(&c.ShowTabs) 78 | 79 | command.Opt("v, show-nonprinting", 80 | "use ^ and M- notation, except for LFD and TAB"). 81 | Flags(flag.PosixShort).Var(&c.ShowNonprinting) 82 | 83 | command.Parse(argv[1:]) 84 | args := command.Args() 85 | 86 | if *showAll { 87 | c.ShowNonprinting = true 88 | c.ShowEnds = true 89 | c.ShowTabs = true 90 | } 91 | 92 | if *e { 93 | c.ShowNonprinting = true 94 | c.ShowEnds = true 95 | } 96 | if *t { 97 | c.ShowNonprinting = true 98 | c.ShowTabs = true 99 | } 100 | 101 | return &c, args 102 | } 103 | 104 | func SetBool(v bool) *bool { 105 | return &v 106 | } 107 | 108 | func (c *Cat) SetTab() { 109 | c.oldNew = append(c.oldNew, "\t", "^I") 110 | } 111 | 112 | func (c *Cat) SetEnds() { 113 | c.oldNew = append(c.oldNew, "\n", "$\n") 114 | } 115 | 116 | func (c *Cat) Cat(rs io.ReadSeeker, w io.Writer) { 117 | br := bufio.NewReader(rs) 118 | replacer := strings.NewReplacer(c.oldNew...) 119 | isSpace := 0 120 | 121 | for count := 1; ; count++ { 122 | 123 | l, e := br.ReadBytes('\n') 124 | if e != nil && len(l) == 0 { 125 | break 126 | } 127 | 128 | if c.SqueezeBlank { 129 | if len(bytes.TrimSpace(l)) == 0 { 130 | isSpace++ 131 | } else { 132 | isSpace = 0 133 | } 134 | 135 | if isSpace > 1 { 136 | count-- 137 | continue 138 | } 139 | } 140 | 141 | if len(c.oldNew) > 0 { 142 | l = []byte(replacer.Replace(string(l))) 143 | } 144 | 145 | if c.ShowNonprinting { 146 | l = writeNonblank(l) 147 | } 148 | 149 | if c.NumberNonblank || c.Number { 150 | 151 | if c.NumberNonblank && len(l) == 1 { 152 | count-- 153 | } 154 | 155 | if !(c.NumberNonblank && len(l) == 1) { 156 | l = append([]byte(fmt.Sprintf("%6d\t", count)), l...) 157 | } 158 | } 159 | 160 | w.Write(l) 161 | } 162 | } 163 | 164 | func Main(argv []string) { 165 | 166 | c, args := New(argv) 167 | 168 | if c.ShowEnds { 169 | c.SetEnds() 170 | } 171 | 172 | if c.ShowTabs { 173 | c.SetTab() 174 | } 175 | 176 | if len(args) > 0 { 177 | for _, fileName := range args { 178 | f, err := utils.OpenFile(fileName) 179 | if err != nil { 180 | utils.Die("cat: %s\n", err) 181 | } 182 | 183 | c.Cat(f, os.Stdout) 184 | f.Close() 185 | } 186 | return 187 | } 188 | c.Cat(os.Stdin, os.Stdout) 189 | } 190 | -------------------------------------------------------------------------------- /cat/cat/cat.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/guonaihong/coreutils/cat" 5 | "os" 6 | ) 7 | 8 | func main() { 9 | cat.Main(os.Args) 10 | } 11 | -------------------------------------------------------------------------------- /cat/cat_test.go: -------------------------------------------------------------------------------- 1 | package cat 2 | 3 | import ( 4 | "bufio" 5 | "bytes" 6 | "fmt" 7 | "strings" 8 | "testing" 9 | ) 10 | 11 | func TestNumber(t *testing.T) { 12 | testNumber := 13 | `1 14 | 2 15 | 3 16 | 4 17 | 5 18 | 6` 19 | 20 | var lastLine []byte 21 | c := Cat{} 22 | 23 | c.Number = true 24 | 25 | in := strings.NewReader(testNumber) 26 | out := &bytes.Buffer{} 27 | 28 | c.Cat(in, out) 29 | 30 | br := bufio.NewReader(strings.NewReader(out.String())) 31 | number := 0 32 | 33 | for { 34 | l, err := br.ReadBytes('\n') 35 | if err != nil && len(l) == 0 { 36 | break 37 | } 38 | lastLine = l 39 | } 40 | 41 | fmt.Sscanf(string(lastLine), "%d", &number) 42 | if number != 6 { 43 | t.Fatalf("cat -n fail lastLine:%d", number) 44 | } 45 | 46 | } 47 | 48 | func TestTab(t *testing.T) { 49 | testTab := "\t\t\t\t\t\t\t\t\n\t\t\t\t\t\n" 50 | c := Cat{} 51 | c.SetTab() 52 | 53 | in := strings.NewReader(testTab) 54 | out := &bytes.Buffer{} 55 | 56 | c.Cat(in, out) 57 | 58 | outStr := out.String() 59 | outStr = strings.Replace(outStr, "^I", "", -1) 60 | outStr = strings.Replace(outStr, "\n", "", -1) 61 | 62 | if len(outStr) != 0 { 63 | t.Fatalf("cat -T fail (%s)", outStr) 64 | 65 | } 66 | } 67 | 68 | func TestEnds(t *testing.T) { 69 | testEnds := `1 70 | 2 71 | 3` 72 | c := Cat{} 73 | c.SetEnds() 74 | 75 | in := strings.NewReader(testEnds) 76 | out := &bytes.Buffer{} 77 | 78 | c.Cat(in, out) 79 | 80 | ls := strings.Split(out.String(), "\n") 81 | for _, v := range ls { 82 | if v == "3" { 83 | continue 84 | } 85 | 86 | if !strings.HasSuffix(v, "$") { 87 | t.Fatalf("cat -E fail (%s)\n", v) 88 | } 89 | } 90 | } 91 | 92 | func TestSqueezeBlank(t *testing.T) { 93 | c := Cat{} 94 | c.SqueezeBlank = true 95 | 96 | rs := strings.NewReader("\n\n\n12\n\n\n\n34\n\n\n") 97 | w := &bytes.Buffer{} 98 | c.Cat(rs, w) 99 | 100 | outStr := ` 101 | 12 102 | 103 | 34 104 | 105 | ` 106 | if w.String() != outStr { 107 | t.Fatalf("cat -s fail(%s)\n", w.String()) 108 | } 109 | } 110 | 111 | func TestNumberNonblank(t *testing.T) { 112 | c := Cat{} 113 | c.NumberNonblank = true 114 | 115 | rs := strings.NewReader("\n\n\n12\n\n\n\n34\n\n\n") 116 | w := &bytes.Buffer{} 117 | c.Cat(rs, w) 118 | outStr := ` 119 | 120 | 121 | 1 12 122 | 123 | 124 | 125 | 2 34 126 | 127 | 128 | ` 129 | if w.String() != outStr { 130 | t.Fatalf("cat -b fail(%s), need(%s)\n", w.String(), outStr) 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /chgrp/README.md: -------------------------------------------------------------------------------- 1 | # chgrp 2 | 3 | #### summary 4 | chgrp has the same chgrp command as ubuntu 18.04 5 | 6 | #### install 7 | ``` 8 | env GOPATH=`pwd` go get -u github.com/guonaihong/coreutils/chgrp/chgrp 9 | ``` 10 | 11 | #### usage 12 | ```console 13 | Usage of chgrp: 14 | -H if a command line argument is a symbolic link 15 | to a directory, traverse it 16 | -L traverse every symbolic link to a directory 17 | encountered 18 | -P do not traverse any symbolic links (default) 19 | -R, --recursive 20 | operate on files and directories recursively 21 | -c, --changes 22 | like verbose but report only when a change is made 23 | -dereference 24 | affect the referent of each symbolic link (this is 25 | the default), rather than the symbolic link itself 26 | -f, --quiet, --silent 27 | suppress most error messages 28 | -from string 29 | change the owner and/or group of each file only if 30 | its current owner and/or group match those specified 31 | here. Either may be omitted, in which case a match 32 | is not required for the omitted attribute 33 | -h, --no-dereference 34 | affect symbolic links instead of any referenced file 35 | (useful only on systems that can change the 36 | ownership of a symlink) 37 | -no-preserve-root 38 | do not treat '/' specially (the default) 39 | -preserve-root 40 | fail to operate recursively on '/' 41 | -reference string 42 | use RFILE's owner and group rather than 43 | specifying OWNER:GROUP values 44 | -v, --verbose 45 | output a diagnostic for every file processed 46 | ``` 47 | -------------------------------------------------------------------------------- /chgrp/chgrp/chgrp.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/guonaihong/coreutils/chgrp" 5 | "os" 6 | ) 7 | 8 | func main() { 9 | chgrp.Main(os.Args) 10 | } 11 | -------------------------------------------------------------------------------- /chgrp/chgrp_test.go: -------------------------------------------------------------------------------- 1 | package chgrp 2 | 3 | import ( 4 | "bytes" 5 | "github.com/guonaihong/coreutils/utils" 6 | "os" 7 | "os/user" 8 | "testing" 9 | ) 10 | 11 | const ( 12 | binGid = 2 13 | rootGid = 0 14 | ) 15 | 16 | func testGetGroupUserError(name string, needErr string, t *testing.T) { 17 | _, err := getGroupFromName(name) 18 | if err.Error() != needErr { 19 | t.Errorf("need error(%s), actual error(%s)\n", needErr, err.Error()) 20 | } 21 | } 22 | 23 | func TestGetGroupUserError(t *testing.T) { 24 | testGetGroupUserError("wwwwwww", "chgrp: invalid group: 'wwwwwww'", t) 25 | } 26 | 27 | func testChgrp(name string, fileName string, needErr string, rootRun bool, t *testing.T) { 28 | u, err := user.Current() 29 | if err != nil { 30 | return 31 | } 32 | 33 | if u.Username == "root" && !rootRun { 34 | return 35 | } 36 | 37 | user := User{} 38 | user.Init(os.Stdout) 39 | c := Chgrp{} 40 | err = c.Chgrp(name, fileName, &user) 41 | if err.Error() != needErr { 42 | t.Errorf("need error(%s), actual error(%s)\n", needErr, err.Error()) 43 | } 44 | } 45 | 46 | func testChgrpVerbose(name string, out string, gid int, t *testing.T) { 47 | u, err := user.Current() 48 | if err != nil { 49 | t.Errorf("%s\n", err) 50 | return 51 | } 52 | 53 | if u.Username != "root" { 54 | t.Errorf("need root user\n") 55 | return 56 | } 57 | 58 | c := Chgrp{} 59 | 60 | var w bytes.Buffer 61 | 62 | user := User{} 63 | user.Init(&w) 64 | os.Chown("test.dat", 2, 2) 65 | 66 | c.Verbose = utils.Bool(true) 67 | err = c.Chgrp(name, "test.dat", &user) 68 | if w.String() != out || w.String() == "" { 69 | t.Errorf("need(%s), actual(%s), rv(%v), name(%s)\n", 70 | out, w.String(), err, name) 71 | } 72 | 73 | if user.Gid != gid { 74 | t.Errorf("name (%s) need gid(%d), actual gid(%d), err(%s)\n", 75 | name, gid, user.Gid, err) 76 | } 77 | } 78 | 79 | // need root user to run 80 | func TestChgrpVerbose(t *testing.T) { 81 | testChgrpVerbose("bin", "group of 'test.dat' retained as bin\n", 2, t) 82 | 83 | testChgrpVerbose("root", "changed group of 'test.dat' from bin to root\n", 0, t) 84 | } 85 | 86 | func testChgrpChanges(name string, out string, gid int, t *testing.T) { 87 | u, err := user.Current() 88 | if err != nil { 89 | t.Errorf("%s\n", err) 90 | return 91 | } 92 | 93 | if u.Username != "root" { 94 | t.Errorf("need root user\n") 95 | return 96 | } 97 | 98 | c := Chgrp{} 99 | 100 | var w bytes.Buffer 101 | 102 | user := User{} 103 | user.Init(&w) 104 | os.Chown("test.dat", 2, 2) 105 | 106 | c.Changes = utils.Bool(true) 107 | err = c.Chgrp(name, "test.dat", &user) 108 | if w.String() != out { 109 | t.Errorf("need(%s), actual(%s), rv(%v), name(%s)\n", 110 | out, w.String(), err, name) 111 | } 112 | 113 | if user.Gid != gid { 114 | t.Errorf("name (%s) need gid(%d), actual gid(%d)\n", 115 | name, gid, user.Gid) 116 | } 117 | } 118 | 119 | func TestChgrpChanges(t *testing.T) { 120 | testChgrpChanges("bin", "", 2, t) 121 | 122 | testChgrpChanges("root", "changed group of 'test.dat' from bin to root\n", 0, t) 123 | } 124 | 125 | func TestChgrp(t *testing.T) { 126 | testChgrp("root", "chgrp_test.go", "chgrp: changing group of 'chgrp_test.go': Operation not permitted", false, t) 127 | } 128 | -------------------------------------------------------------------------------- /chgrp/test.dat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guonaihong/coreutils/0372a04a1d2dd0e8f3136d85e901f62fdde5cd41/chgrp/test.dat -------------------------------------------------------------------------------- /chmod/chmod.go: -------------------------------------------------------------------------------- 1 | package chmod 2 | 3 | import ( 4 | "fmt" 5 | "github.com/guonaihong/flag" 6 | ) 7 | 8 | type Chmod struct { 9 | Changes *bool 10 | Quiet *bool 11 | Verbose *bool 12 | NoPreserveRoot *bool 13 | PreserveRoot *bool 14 | Reference *string 15 | Recursive *bool 16 | } 17 | 18 | func New(argv []string) (*Chmod, []string) { 19 | c := &Chmod{} 20 | 21 | command := flag.NewFlagSet(argv[0]) 22 | 23 | c.Changes = command.Opt("c, changes", 24 | "like verbose but report only when a change is made") 25 | 26 | c.Quiet = command.Opt("f, silent, quiet", 27 | "suppress most error messages") 28 | 29 | c.Verbose = command.Opt("v, verbose", 30 | "output a diagnostic for every file processed") 31 | 32 | c.NoPreserveRoot = command.Opt("no-preserve-root", 33 | "do not treat '/' specially (the default)") 34 | 35 | c.PreserveRoot = command.Opt("preserve-root", 36 | "fail to operate recursively on '/'") 37 | 38 | c.Reference = command.Opt("reference", 39 | "use RFILE's mode instead of MODE values") 40 | 41 | c.Recursive = command.Opt("R, recursive", 42 | "change files and directories recursively") 43 | 44 | command.Parse(argv[1:]) 45 | 46 | return c, command.Args() 47 | } 48 | 49 | func main() { 50 | fmt.Println("vim-go") 51 | } 52 | -------------------------------------------------------------------------------- /chown/README.md: -------------------------------------------------------------------------------- 1 | # chown 2 | 3 | #### summary 4 | chown has the same chown command as ubuntu 18.04 5 | 6 | #### install 7 | ``` 8 | env GOPATH=`pwd` go get -u github.com/guonaihong/coreutils/chown/chown 9 | ``` 10 | 11 | #### usage 12 | ```console 13 | Usage of chown: 14 | -H if a command line argument is a symbolic link 15 | to a directory, traverse it 16 | -L traverse every symbolic link to a directory 17 | encountered 18 | -P do not traverse any symbolic links (default) 19 | -R, --recursive 20 | operate on files and directories recursively 21 | -c, --changes 22 | like verbose but report only when a change is made 23 | -dereference 24 | affect the referent of each symbolic link (this is 25 | the default), rather than the symbolic link itself 26 | -f, --quiet, --silent 27 | suppress most error messages 28 | -from string 29 | change the owner and/or group of each file only if 30 | its current owner and/or group match those specified 31 | here. Either may be omitted, in which case a match 32 | is not required for the omitted attribute 33 | -h, --no-dereference 34 | affect symbolic links instead of any referenced file 35 | (useful only on systems that can change the 36 | ownership of a symlink) 37 | -no-preserve-root 38 | do not treat '/' specially (the default) 39 | -preserve-root 40 | fail to operate recursively on '/' 41 | -reference string 42 | use RFILE's owner and group rather than 43 | specifying OWNER:GROUP values 44 | -v, --verbose 45 | output a diagnostic for every file processed 46 | 47 | ``` 48 | -------------------------------------------------------------------------------- /chown/chown/chown.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/guonaihong/coreutils/chown" 5 | "os" 6 | ) 7 | 8 | func main() { 9 | chown.Main(os.Args) 10 | } 11 | -------------------------------------------------------------------------------- /chown/chown_test.go: -------------------------------------------------------------------------------- 1 | package chown 2 | 3 | import ( 4 | "bytes" 5 | "github.com/guonaihong/coreutils/utils" 6 | "os" 7 | "os/user" 8 | "testing" 9 | ) 10 | 11 | const ( 12 | binUid = 2 13 | binGid = 2 14 | rootUid = 0 15 | rootGid = 0 16 | ) 17 | 18 | // test cmd 19 | //./coreutils chown -v -R guo:guo ./nsq 20 | 21 | func testGetGroupUserError(name string, needErr string, t *testing.T) { 22 | _, err := getUserGroupFromName(name) 23 | if err.Error() != needErr { 24 | t.Errorf("need error(%s), actual error(%s)\n", needErr, err.Error()) 25 | } 26 | } 27 | 28 | func TestGetGroupUserError(t *testing.T) { 29 | testGetGroupUserError("wwwwwww:root", "chown: invalid user: 'wwwwwww:root'", t) 30 | testGetGroupUserError("root:wwwwwww", "chown: invalid group: 'root:wwwwwww'", t) 31 | } 32 | 33 | func testChown(name string, fileName string, needErr string, rootRun bool, t *testing.T) { 34 | u, err := user.Current() 35 | if err != nil { 36 | return 37 | } 38 | 39 | if u.Username == "root" && !rootRun { 40 | return 41 | } 42 | 43 | user := User{} 44 | user.Init(os.Stdout) 45 | c := Chown{} 46 | err = c.Chown(name, fileName, &user) 47 | if err.Error() != needErr { 48 | t.Errorf("need error(%s), actual error(%s)\n", needErr, err.Error()) 49 | } 50 | } 51 | 52 | func testChownVerbose(name string, out string, uid int, gid int, t *testing.T) { 53 | u, err := user.Current() 54 | if err != nil { 55 | t.Errorf("%s\n", err) 56 | return 57 | } 58 | 59 | if u.Username != "root" { 60 | t.Errorf("need root user\n") 61 | return 62 | } 63 | 64 | c := Chown{} 65 | 66 | var w bytes.Buffer 67 | 68 | user := User{} 69 | user.Init(&w) 70 | os.Chown("test.dat", 2, 2) 71 | 72 | c.Verbose = utils.Bool(true) 73 | err = c.Chown(name, "test.dat", &user) 74 | if w.String() != out || w.String() == "" { 75 | t.Errorf("need(%s), actual(%s), rv(%v), name(%s)\n", 76 | out, w.String(), err, name) 77 | } 78 | 79 | if user.Uid != uid || user.Gid != gid { 80 | t.Errorf("name (%s) need uid(%d) gid(%d), actual uid(%d) gid(%d)\n", 81 | name, uid, gid, user.Uid, user.Gid) 82 | } 83 | } 84 | 85 | // need root user to run 86 | func TestChownVerbose(t *testing.T) { 87 | testChownVerbose(":", "ownership of 'test.dat' retained\n", -1, -1, t) 88 | testChownVerbose("bin", "ownership of 'test.dat' retained as bin\n", binUid, -1, t) 89 | testChownVerbose("bin:", "ownership of 'test.dat' retained as bin:bin\n", binUid, binGid, t) 90 | testChownVerbose(":bin", "ownership of 'test.dat' retained as :bin\n", -1, binUid, t) 91 | 92 | testChownVerbose("root:", "changed ownership of 'test.dat' from bin:bin to root:root\n", rootUid, rootGid, t) 93 | testChownVerbose(":root", "changed ownership of 'test.dat' from bin:bin to :root\n", -1, rootGid, t) 94 | 95 | testChownVerbose("root", "changed ownership of 'test.dat' from bin to root\n", rootUid, -1, t) 96 | } 97 | 98 | func testChownChanges(name string, out string, uid int, gid int, t *testing.T) { 99 | u, err := user.Current() 100 | if err != nil { 101 | t.Errorf("%s\n", err) 102 | return 103 | } 104 | 105 | if u.Username != "root" { 106 | t.Errorf("need root user\n") 107 | return 108 | } 109 | 110 | c := Chown{} 111 | 112 | var w bytes.Buffer 113 | 114 | user := User{} 115 | user.Init(&w) 116 | os.Chown("test.dat", 2, 2) 117 | 118 | c.Changes = utils.Bool(true) 119 | err = c.Chown(name, "test.dat", &user) 120 | if w.String() != out { 121 | t.Errorf("need(%s), actual(%s), rv(%v), name(%s)\n", 122 | out, w.String(), err, name) 123 | } 124 | 125 | if user.Uid != uid || user.Gid != gid { 126 | t.Errorf("name (%s) need uid(%d) gid(%d), actual uid(%d) gid(%d)\n", 127 | name, uid, gid, user.Uid, user.Gid) 128 | } 129 | } 130 | 131 | func TestChownChanges(t *testing.T) { 132 | testChownChanges(":", "", -1, -1, t) 133 | testChownChanges("bin", "", binUid, -1, t) 134 | testChownChanges("bin:", "", binUid, binGid, t) 135 | testChownChanges(":bin", "", -1, binUid, t) 136 | 137 | testChownChanges("root:", "changed ownership of 'test.dat' from bin:bin to root:root\n", rootUid, rootGid, t) 138 | testChownChanges(":root", "changed ownership of 'test.dat' from bin:bin to :root\n", -1, rootGid, t) 139 | 140 | testChownChanges("root", "changed ownership of 'test.dat' from bin to root\n", rootUid, -1, t) 141 | } 142 | 143 | func TestChown(t *testing.T) { 144 | testChown("root", "chown_test.go", "chown: changing ownership of 'chown_test.go': Operation not permitted", false, t) 145 | testChown(":", "yy", "chown: cannot access 'yy': No such file or directory", true, t) 146 | } 147 | -------------------------------------------------------------------------------- /chown/test.dat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guonaihong/coreutils/0372a04a1d2dd0e8f3136d85e901f62fdde5cd41/chown/test.dat -------------------------------------------------------------------------------- /coreutils/coreutils.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/guonaihong/coreutils/base32" 5 | "github.com/guonaihong/coreutils/base64" 6 | "github.com/guonaihong/coreutils/basename" 7 | "github.com/guonaihong/coreutils/cat" 8 | "github.com/guonaihong/coreutils/chgrp" 9 | "github.com/guonaihong/coreutils/chown" 10 | "github.com/guonaihong/coreutils/cut" 11 | "github.com/guonaihong/coreutils/dirname" 12 | "github.com/guonaihong/coreutils/echo" 13 | "github.com/guonaihong/coreutils/env" 14 | "github.com/guonaihong/coreutils/head" 15 | "github.com/guonaihong/coreutils/link" 16 | "github.com/guonaihong/coreutils/md5sum" 17 | "github.com/guonaihong/coreutils/paste" 18 | "github.com/guonaihong/coreutils/pwd" 19 | "github.com/guonaihong/coreutils/rmdir" 20 | "github.com/guonaihong/coreutils/seq" 21 | "github.com/guonaihong/coreutils/sha1sum" 22 | "github.com/guonaihong/coreutils/sha224sum" 23 | "github.com/guonaihong/coreutils/sha256sum" 24 | "github.com/guonaihong/coreutils/sha384sum" 25 | "github.com/guonaihong/coreutils/sha512sum" 26 | "github.com/guonaihong/coreutils/shuf" 27 | "github.com/guonaihong/coreutils/sleep" 28 | "github.com/guonaihong/coreutils/tac" 29 | "github.com/guonaihong/coreutils/tail" 30 | "github.com/guonaihong/coreutils/tee" 31 | "github.com/guonaihong/coreutils/touch" 32 | "github.com/guonaihong/coreutils/tr" 33 | "github.com/guonaihong/coreutils/true" 34 | "github.com/guonaihong/coreutils/uname" 35 | "github.com/guonaihong/coreutils/uniq" 36 | "github.com/guonaihong/coreutils/unlink" 37 | "github.com/guonaihong/coreutils/whoami" 38 | "github.com/guonaihong/coreutils/yes" 39 | "github.com/guonaihong/flag" 40 | "os" 41 | ) 42 | 43 | func main() { 44 | parent := flag.NewParentCommand(os.Args[0]) 45 | 46 | parent.SubCommand("base32", "Use the base32 subcommand", func() { 47 | base32.Main(os.Args[1:]) 48 | }) 49 | 50 | parent.SubCommand("base64", "Use the base64 subcommand", func() { 51 | base64.Main(os.Args[1:]) 52 | }) 53 | 54 | parent.SubCommand("cat", "Use the cat subcommand", func() { 55 | cat.Main(os.Args[1:]) 56 | }) 57 | 58 | parent.SubCommand("chown", "Use the chown subcommand", func() { 59 | chown.Main(os.Args[1:]) 60 | }) 61 | 62 | parent.SubCommand("chgrp", "Use the chgrp subcommand", func() { 63 | chgrp.Main(os.Args[1:]) 64 | }) 65 | 66 | parent.SubCommand("cut", "Use the cut subcommand", func() { 67 | cut.Main(os.Args[1:]) 68 | }) 69 | 70 | parent.SubCommand("paste", "Use the paste subcommand", func() { 71 | paste.Main(os.Args[1:]) 72 | }) 73 | 74 | parent.SubCommand("pwd", "Use the pwd subcommand", func() { 75 | pwd.Main(os.Args[1:]) 76 | }) 77 | 78 | parent.SubCommand("rmdir", "Use the rmdir subcommand", func() { 79 | rmdir.Main(os.Args[1:]) 80 | }) 81 | 82 | parent.SubCommand("basename", "Use the basename subcommand", func() { 83 | basename.Main(os.Args[1:]) 84 | }) 85 | 86 | parent.SubCommand("dirname", "Use the dirname subcommand", func() { 87 | dirname.Main(os.Args[1:]) 88 | }) 89 | 90 | parent.SubCommand("echo", "Use the echo subcommand", func() { 91 | echo.Main(os.Args[1:]) 92 | }) 93 | 94 | parent.SubCommand("env", "Use the env subcommand", func() { 95 | env.Main(os.Args[1:]) 96 | }) 97 | 98 | parent.SubCommand("head", "Use the head subcommand", func() { 99 | head.Main(os.Args[1:]) 100 | }) 101 | 102 | parent.SubCommand("link", "Use the link subcommand", func() { 103 | link.Main(os.Args[1:]) 104 | }) 105 | 106 | parent.SubCommand("md5sum", "Use the md5sum subcommand", func() { 107 | md5sum.Main(os.Args[0:]) 108 | }) 109 | 110 | parent.SubCommand("seq", "Use the seq subcommand", func() { 111 | seq.Main(os.Args[1:]) 112 | }) 113 | 114 | parent.SubCommand("sha1sum", "Use the sha1sum subcommand", func() { 115 | sha1sum.Main(os.Args[1:]) 116 | }) 117 | 118 | parent.SubCommand("sha224sum", "Use the sha224sum subcommand", func() { 119 | sha224sum.Main(os.Args[1:]) 120 | }) 121 | 122 | parent.SubCommand("sha256sum", "Use the sha256sum subcommand", func() { 123 | sha256sum.Main(os.Args[1:]) 124 | }) 125 | 126 | parent.SubCommand("sha384sum", "Use the sha384sum subcommand", func() { 127 | sha384sum.Main(os.Args[1:]) 128 | }) 129 | 130 | parent.SubCommand("sha512sum", "Use the sha512sum subcommand", func() { 131 | sha512sum.Main(os.Args[1:]) 132 | }) 133 | 134 | parent.SubCommand("shuf", "Use the shuf subcommand", func() { 135 | shuf.Main(os.Args[1:]) 136 | }) 137 | 138 | parent.SubCommand("sleep", "Use the sleep subcommand", func() { 139 | sleep.Main(os.Args[1:]) 140 | }) 141 | 142 | parent.SubCommand("tac", "Use the tac subcommand", func() { 143 | tac.Main(os.Args[1:]) 144 | }) 145 | 146 | parent.SubCommand("tail", "Use the tail subcommand", func() { 147 | tail.Main(os.Args[1:]) 148 | }) 149 | 150 | parent.SubCommand("tee", "Use the tee subcommand", func() { 151 | tee.Main(os.Args[1:]) 152 | }) 153 | 154 | parent.SubCommand("touch", "Use the touch subcommand", func() { 155 | touch.Main(os.Args[1:]) 156 | }) 157 | 158 | parent.SubCommand("tr", "Use the tr subcommand", func() { 159 | tr.Main(os.Args[1:]) 160 | }) 161 | 162 | parent.SubCommand("true", "Use the true subcommand", func() { 163 | true.Main(os.Args[1:]) 164 | }) 165 | 166 | parent.SubCommand("uname", "Use the uname subcommand", func() { 167 | uname.Main(os.Args[1:]) 168 | }) 169 | 170 | parent.SubCommand("uniq", "Use the uniq subcommand", func() { 171 | uniq.Main(os.Args[1:]) 172 | }) 173 | 174 | parent.SubCommand("unlink", "Use the unlink subcommand", func() { 175 | unlink.Main(os.Args[1:]) 176 | }) 177 | 178 | parent.SubCommand("whoami", "Use the whoami subcommand", func() { 179 | whoami.Main(os.Args[1:]) 180 | }) 181 | 182 | parent.SubCommand("yes", "Use the yes subcommand", func() { 183 | yes.Main(os.Args[1:]) 184 | }) 185 | 186 | parent.Parse(os.Args[1:]) 187 | } 188 | -------------------------------------------------------------------------------- /cut/README.md: -------------------------------------------------------------------------------- 1 | # cut 2 | 3 | #### summary 4 | cut 5 | 6 | #### install 7 | ``` 8 | env GOPATH=`pwd` go get -u github.com/guonaihong/coreutils/cut/cut 9 | ``` 10 | 11 | #### usage 12 | ```console 13 | Usage of cut: 14 | -b, --bytes string 15 | select only these bytes 16 | -c, --characters string 17 | select only these characters 18 | -complement 19 | complement the set of selected bytes, characters 20 | or fields 21 | -d, --delimiter string 22 | use DELIM instead of TAB for field delimiter 23 | -f, --fields string 24 | select only these fields; also print any line 25 | that contains no delimiter character, unless 26 | the -s option is specified 27 | -output-delimiter string 28 | use STRING as the output delimiter 29 | the default is to use the input delimiter 30 | -s, --only-delimited 31 | do not print lines not containing delimiters 32 | -zero-terminated 33 | line delimiter is NUL, not newline 34 | ``` 35 | 36 | #### Example 37 | (以下内容来自linux shell Scripting Cookbook) 38 | 39 | 这条命令将显示第2列和第3列 40 | ```shell 41 | cut -f 2,3 filename 42 | ``` 43 | 44 | cut也能从stdint中读取输入文本。 45 | \t是字段或列的默认分割符。对于分割符的行。会将该行照原样打印出来。如果不想打印出这种不包含分割符的行, 46 | 则可以使用cut的 -s选项。 47 | 48 | 我们也可以使用--complement选项对提取的字段进行补集运算。假设有多个字段, 49 | 你希望打印出除第3列之外的所有列,则可以使用: 50 | ```shell 51 | cut -f3 --complement student_data 52 | ``` 53 | 54 | 要指定分割符,使用-d选项 55 | ``` 56 | cat delimited_data.txt 57 | No;Name;Mark;Percent 58 | 1;Sarath;45;90 59 | 60 | cut -f 2 -d ';' delimited_data.txt 61 | ``` 62 | 63 | 假设我们不依赖分割符,但需要通过将字段定义为一个字符范围来进行字段提取 64 | ``` 65 | N- 从第N个字节,字符或字段到行尾 66 | N-M 从第N个字节,字符或字段到第M个(包括第M个在内)字节、字符或字段 67 | -M 第1个字节,字符或字段到第M个(包括第M个在内)字节、字符或字段 68 | ``` 69 | 70 | 打印第1到5个字符 71 | ``` 72 | cut -c1-5 filename 73 | ``` 74 | 75 | 打印前2个字符 76 | ``` 77 | cut filename -c -2 78 | ``` 79 | -------------------------------------------------------------------------------- /cut/cut.go: -------------------------------------------------------------------------------- 1 | package cut 2 | 3 | import ( 4 | "bufio" 5 | "bytes" 6 | "fmt" 7 | "github.com/guonaihong/coreutils/utils" 8 | "github.com/guonaihong/flag" 9 | "io" 10 | "math" 11 | "os" 12 | "strconv" 13 | "strings" 14 | ) 15 | 16 | type Cut struct { 17 | Bytes string 18 | Characters string 19 | Delimiter string 20 | Fields string 21 | Complement bool 22 | OnlyDelimited bool 23 | OutputDelimiter string 24 | LineDelim byte 25 | filterCtrl 26 | } 27 | 28 | func New(argv []string) (*Cut, []string) { 29 | command := flag.NewFlagSet(argv[0], flag.ExitOnError) 30 | 31 | c := Cut{} 32 | 33 | command.Opt("b, bytes", "select only these bytes"). 34 | Flags(flag.PosixShort). 35 | Var(&c.Bytes) 36 | 37 | command.Opt("c, characters", "select only these characters"). 38 | Flags(flag.PosixShort). 39 | Var(&c.Characters) 40 | 41 | command.Opt("d, delimiter", "use DELIM instead of TAB for field delimiter"). 42 | Flags(flag.PosixShort). 43 | DefaultVar(&c.Delimiter, "\t") 44 | 45 | command.Opt("f, fields", "select only these fields; also print any line\n"+ 46 | " that contains no delimiter character, unless\n"+ 47 | " the -s option is specified"). 48 | Flags(flag.PosixShort). 49 | Var(&c.Fields) 50 | 51 | command.Opt("complement", "complement the set of selected bytes, characters\n"+ 52 | "or fields"). 53 | Flags(flag.PosixShort). 54 | Var(&c.Complement) 55 | 56 | command.Opt("s, only-delimited", "do not print lines not containing delimiters"). 57 | Flags(flag.PosixShort). 58 | Var(&c.OnlyDelimited) 59 | 60 | command.Opt("output-delimiter", "use STRING as the output delimiter\n"+ 61 | "the default is to use the input delimiter"). 62 | Flags(flag.PosixShort). 63 | Var(&c.OutputDelimiter) 64 | 65 | zeroTerminated := command.Opt("zero-terminated", "line delimiter is NUL, not newline"). 66 | Flags(flag.PosixShort). 67 | NewBool(false) 68 | 69 | command.Parse(argv[1:]) 70 | 71 | args := command.Args() 72 | 73 | c.LineDelim = byte('\n') 74 | if *zeroTerminated { 75 | c.LineDelim = '\000' 76 | } 77 | 78 | return &c, args 79 | } 80 | 81 | type paragraph struct { 82 | start, end int 83 | } 84 | 85 | type filterCtrl struct { 86 | filter map[int]struct{} 87 | p []paragraph 88 | } 89 | 90 | func die(filter string) { 91 | fmt.Printf(`cut: invalid field value "%s"\n`, filter) 92 | os.Exit(1) 93 | } 94 | 95 | func (f *filterCtrl) init(filter string) { 96 | var err error 97 | f.filter = map[int]struct{}{} 98 | s := strings.Split(filter, ",") 99 | 100 | start, end := 0, 0 101 | 102 | for _, v := range s { 103 | startEnd := strings.Split(v, "-") 104 | 105 | if len(startEnd) == 2 { 106 | p := paragraph{} 107 | 108 | if len(startEnd[0]) == 0 { 109 | p.start = 1 110 | } 111 | 112 | if len(startEnd[1]) == 0 { 113 | p.end = math.MaxInt64 114 | } 115 | 116 | if len(startEnd[0]) > 0 { 117 | start, err = strconv.Atoi(startEnd[0]) 118 | if err != nil { 119 | die(filter) 120 | } 121 | 122 | if p.start == 0 && start > 0 { 123 | p.start = start 124 | } 125 | 126 | if start > 0 && start < p.start { 127 | p.start = start 128 | } 129 | } 130 | 131 | if len(startEnd[1]) > 0 { 132 | end, err = strconv.Atoi(startEnd[1]) 133 | if err != nil { 134 | die(filter) 135 | } 136 | if end > p.end { 137 | p.end = end 138 | } 139 | } 140 | 141 | f.p = append(f.p, p) 142 | continue 143 | } 144 | 145 | n, err := strconv.Atoi(v) 146 | if err != nil { 147 | die(v) 148 | } 149 | 150 | f.filter[n] = struct{}{} 151 | } 152 | } 153 | 154 | func (f *filterCtrl) check(index int) (ok bool) { 155 | 156 | for _, p := range f.p { 157 | if index >= p.start && index <= p.end { 158 | return true 159 | } 160 | } 161 | 162 | _, ok = f.filter[index] 163 | return ok 164 | } 165 | 166 | func (c *Cut) Cut(rs io.ReadSeeker, w io.Writer) { 167 | reader := bufio.NewReader(rs) 168 | buf := bytes.Buffer{} 169 | output := [][]byte{} 170 | byteOutput := []byte{} 171 | 172 | defer func() { 173 | if buf.Len() > 0 { 174 | w.Write(buf.Bytes()) 175 | } 176 | }() 177 | 178 | for { 179 | line, err := reader.ReadBytes(c.LineDelim) 180 | if err != nil && len(line) == 0 { 181 | break 182 | } 183 | 184 | have := false 185 | if c.isFields() { 186 | ls := bytes.Split(line, []byte(c.Delimiter)) 187 | if len(ls) == 1 { 188 | if c.OnlyDelimited { 189 | continue 190 | } 191 | buf.Write(line) 192 | goto write 193 | } 194 | 195 | for i, v := range ls { 196 | checkOk := c.check(i + 1) 197 | if c.Complement { 198 | checkOk = !checkOk 199 | } 200 | 201 | if checkOk { 202 | have = true 203 | output = append(output, v) 204 | } 205 | } 206 | 207 | buf.Write(bytes.Join(output, []byte(c.OutputDelimiter))) 208 | output = output[:0] 209 | 210 | goto write 211 | } 212 | 213 | for i, v := range line { 214 | checkOk := c.check(i + 1) 215 | if c.Complement { 216 | checkOk = !checkOk 217 | } 218 | 219 | if checkOk { 220 | have = true 221 | byteOutput = append(byteOutput, v) 222 | } 223 | } 224 | 225 | buf.Write(byteOutput) 226 | byteOutput = byteOutput[:0] 227 | 228 | write: 229 | if have { 230 | if buf.Bytes()[buf.Len()-1] != c.LineDelim && line[len(line)-1] == c.LineDelim { 231 | buf.WriteByte(c.LineDelim) 232 | } 233 | } 234 | 235 | if buf.Len() >= 0 { 236 | w.Write(buf.Bytes()) 237 | buf.Reset() 238 | } 239 | } 240 | } 241 | 242 | func (c *Cut) isBytes() bool { 243 | return len(c.Bytes) > 0 244 | } 245 | 246 | func (c *Cut) isCharacters() bool { 247 | return len(c.Characters) > 0 248 | } 249 | 250 | func (c *Cut) isFields() bool { 251 | return len(c.Fields) > 0 252 | } 253 | 254 | func (c *Cut) isDelimiter() bool { 255 | return len(c.Delimiter) > 0 256 | } 257 | 258 | func (c *Cut) isOutputDelimiter() bool { 259 | return len(c.OutputDelimiter) > 0 260 | } 261 | 262 | func (c *Cut) Init() { 263 | checkFiledsNum := func() { 264 | filedsCount := 0 265 | 266 | if c.isBytes() { 267 | filedsCount++ 268 | } 269 | 270 | if c.isCharacters() { 271 | filedsCount++ 272 | } 273 | 274 | if c.isFields() { 275 | filedsCount++ 276 | } 277 | 278 | if filedsCount >= 2 { 279 | utils.Die("only one type of list may be specified\n") 280 | } 281 | 282 | if filedsCount == 0 { 283 | utils.Die("you must specify a list of bytes, characters, or fields\n") 284 | } 285 | } 286 | 287 | checkFiledsNum() 288 | 289 | if c.isBytes() { 290 | c.Characters = c.Bytes 291 | } 292 | 293 | if c.isFields() { 294 | c.init(c.Fields) 295 | if c.isDelimiter() && c.OutputDelimiter == "" { 296 | c.OutputDelimiter = c.Delimiter 297 | } 298 | return 299 | } 300 | 301 | c.init(c.Characters) 302 | 303 | } 304 | 305 | func Main(argv []string) { 306 | 307 | c, args := New(argv) 308 | 309 | c.Init() 310 | if len(args) == 0 { 311 | args = append(args, "-") 312 | } 313 | 314 | for _, v := range args { 315 | fd, err := utils.OpenFile(v) 316 | if err != nil { 317 | utils.Die("cut: %s\n", err) 318 | } 319 | 320 | c.Cut(fd, os.Stdout) 321 | fd.Close() 322 | } 323 | } 324 | -------------------------------------------------------------------------------- /cut/cut/cut.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/guonaihong/coreutils/cut" 5 | "os" 6 | ) 7 | 8 | func main() { 9 | cut.Main(os.Args) 10 | } 11 | -------------------------------------------------------------------------------- /cut/cut_test.go: -------------------------------------------------------------------------------- 1 | package cut 2 | 3 | import ( 4 | "bytes" 5 | "strings" 6 | "testing" 7 | ) 8 | 9 | func testBytes(src, dst, b string, t *testing.T) { 10 | w := &bytes.Buffer{} 11 | 12 | c := Cut{} 13 | c.Bytes = b 14 | c.LineDelim = '\n' 15 | c.Init() 16 | c.Cut(strings.NewReader(src), w) 17 | if w.String() != dst { 18 | t.Errorf("cut -b fail(%s)(%x)(%x), need(%s)", w.String(), w.String(), w.Len(), dst) 19 | } 20 | } 21 | 22 | func TestBytes(t *testing.T) { 23 | src := "12345678910" 24 | dst := "1234567" 25 | 26 | testBytes(src, dst, "1-5,3,6-7", t) 27 | //==================== 28 | 29 | src = `Andhra Pradesh 30 | Arunachal Pradesh 31 | Assam 32 | Bihar 33 | Chhattisgarh` 34 | dst = `And 35 | Aru 36 | Ass 37 | Bih 38 | Chh` 39 | testBytes(src, dst, "1,2,3", t) 40 | //======================== 41 | 42 | dst = `Andra 43 | Aruach 44 | Assm 45 | Bihr 46 | Chhtti` 47 | 48 | testBytes(src, dst, "1-3,5-7", t) 49 | 50 | dst = `Andhra Pradesh 51 | Arunachal Pradesh 52 | Assam 53 | Bihar 54 | Chhattisgarh` 55 | 56 | testBytes(src, dst, "1-", t) 57 | 58 | dst = `And 59 | Aru 60 | Ass 61 | Bih 62 | Chh` 63 | 64 | testBytes(src, dst, "-3", t) 65 | } 66 | 67 | func testCharacters(src, dst, cstr string, t *testing.T) { 68 | w := &bytes.Buffer{} 69 | 70 | c := Cut{} 71 | c.Characters = cstr 72 | c.LineDelim = '\n' 73 | c.Init() 74 | c.Cut(strings.NewReader(src), w) 75 | if w.String() != dst { 76 | t.Errorf("cut -c fail(%s)(%x)(%x), need(%s)", w.String(), w.String(), w.Len(), dst) 77 | } 78 | } 79 | 80 | func TestCharacters(t *testing.T) { 81 | src := `Andhra Pradesh 82 | Arunachal Pradesh 83 | Assam 84 | Bihar 85 | Chhattisgarh` 86 | dst := `nr 87 | rah 88 | sm 89 | ir 90 | hti` 91 | 92 | testCharacters(src, dst, "2,5,7", t) 93 | 94 | dst = `Andhra 95 | Arunach 96 | Assam 97 | Bihar 98 | Chhatti` 99 | 100 | testCharacters(src, dst, "1-7", t) 101 | 102 | dst = `Andhra Pradesh 103 | Arunachal Pradesh 104 | Assam 105 | Bihar 106 | Chhattisgarh` 107 | 108 | testCharacters(src, dst, "1-", t) 109 | 110 | dst = `Andhr 111 | Aruna 112 | Assam 113 | Bihar 114 | Chhat` 115 | testCharacters(src, dst, "-5", t) 116 | 117 | } 118 | 119 | func testFieldsDelimiter(src, dst string, delimiter string, fields string, t *testing.T) { 120 | w := &bytes.Buffer{} 121 | 122 | c := Cut{} 123 | c.Delimiter = delimiter 124 | c.Fields = fields 125 | c.LineDelim = '\n' 126 | c.Init() 127 | c.Cut(strings.NewReader(src), w) 128 | if w.String() != dst { 129 | t.Errorf("cut -f -d fail(%s)(%x)(%x), need(%s)", w.String(), w.String(), w.Len(), dst) 130 | } 131 | } 132 | 133 | func TestFieldsDelimiter(t *testing.T) { 134 | src := `Andhra Pradesh 135 | Arunachal Pradesh 136 | Assam 137 | Bihar 138 | Chhattisgarh` 139 | dst := `Andhra Pradesh 140 | Arunachal Pradesh 141 | Assam 142 | Bihar 143 | Chhattisgarh` 144 | 145 | testFieldsDelimiter(src, dst, "\t", "1", t) 146 | 147 | dst = `Andhra 148 | Arunachal 149 | Assam 150 | Bihar 151 | Chhattisgarh` 152 | testFieldsDelimiter(src, dst, " ", "1", t) 153 | //======================================= 154 | 155 | dst = `Andhra Pradesh 156 | Arunachal Pradesh 157 | Assam 158 | Bihar 159 | Chhattisgarh` 160 | testFieldsDelimiter(src, dst, " ", "1-4", t) 161 | } 162 | 163 | func testComplement(complement bool, src, dst string, delimiter string, fields string, t *testing.T) { 164 | w := &bytes.Buffer{} 165 | 166 | c := Cut{} 167 | c.Complement = complement 168 | c.Delimiter = delimiter 169 | c.Fields = fields 170 | c.LineDelim = '\n' 171 | c.Init() 172 | c.Cut(strings.NewReader(src), w) 173 | if w.String() != dst { 174 | t.Errorf("cut -complement fail(%s)(%x)(%x), need(%s)", w.String(), w.String(), w.Len(), dst) 175 | } 176 | } 177 | 178 | func testComplement2(complement bool, src, dst string, characters string, t *testing.T) { 179 | w := &bytes.Buffer{} 180 | 181 | c := Cut{} 182 | c.Complement = complement 183 | c.Characters = characters 184 | c.LineDelim = '\n' 185 | c.Init() 186 | c.Cut(strings.NewReader(src), w) 187 | if w.String() != dst { 188 | t.Errorf("cut -complement fail(%s)(%x)(%x), need(%s)", w.String(), w.String(), w.Len(), dst) 189 | } 190 | } 191 | func TestComplement(t *testing.T) { 192 | src := `Andhra Pradesh 193 | Arunachal Pradesh 194 | Assam 195 | Bihar 196 | Chhattisgarh` 197 | 198 | dst := `Pradesh 199 | Pradesh 200 | Assam 201 | Bihar 202 | Chhattisgarh` 203 | testComplement(true, src, dst, " ", "1", t) 204 | //========= 205 | 206 | dst = `Andha Pradesh 207 | Arunchal Pradesh 208 | Assa 209 | Biha 210 | Chhatisgarh` 211 | testComplement2(true, src, dst, "5", t) 212 | } 213 | 214 | func testOutputDelimiter(src, dst string, delimiter string, fields string, outputDelimiter string, t *testing.T) { 215 | w := &bytes.Buffer{} 216 | 217 | c := Cut{} 218 | c.Delimiter = delimiter 219 | c.Fields = fields 220 | c.OutputDelimiter = outputDelimiter 221 | c.LineDelim = '\n' 222 | c.Init() 223 | c.Cut(strings.NewReader(src), w) 224 | if w.String() != dst { 225 | t.Errorf("cut -output-delimiter fail(%s)(%x)(%x), need(%s)", w.String(), w.String(), w.Len(), dst) 226 | } 227 | } 228 | 229 | func TestOutputDelimiter(t *testing.T) { 230 | src := `Andhra Pradesh 231 | Arunachal Pradesh 232 | Assam 233 | Bihar 234 | Chhattisgarh` 235 | 236 | dst := `Andhra%Pradesh 237 | Arunachal%Pradesh 238 | Assam 239 | Bihar 240 | Chhattisgarh` 241 | 242 | testOutputDelimiter(src, dst, " ", "1,2", "%", t) 243 | } 244 | 245 | func TestCmdOption(t *testing.T) { 246 | c, _ := New([]string{"cut", "-f3"}) 247 | if c.Fields == "" || c.Fields != "3" { 248 | t.Errorf("cut -f options fail\n") 249 | } 250 | 251 | c, _ = New([]string{"cut", "-c1-5"}) 252 | if c.Characters == "" || c.Characters != "1-5" { 253 | t.Errorf("cut -c options fail\n") 254 | } 255 | 256 | c, _ = New([]string{"cut", "-c-5"}) 257 | if c.Characters == "" || c.Characters != "-5" { 258 | t.Errorf("cut -c options fail\n") 259 | } 260 | } 261 | -------------------------------------------------------------------------------- /dirname/README.md: -------------------------------------------------------------------------------- 1 | # dirname 2 | 3 | #### summary 4 | dirname 5 | 6 | #### install 7 | ``` 8 | env GOPATH=`pwd` go get -u github.com/guonaihong/coreutils/dirname/dirname 9 | ``` 10 | 11 | #### usage 12 | ```console 13 | Usage of dirname: 14 | -version 15 | output version information and exit 16 | -z, --zero 17 | end each output line with NUL, not newline 18 | ``` 19 | -------------------------------------------------------------------------------- /dirname/dirname.go: -------------------------------------------------------------------------------- 1 | package dirname 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "github.com/guonaihong/flag" 7 | "os" 8 | "path/filepath" 9 | ) 10 | 11 | func Main(argv []string) { 12 | command := flag.NewFlagSet(argv[0], flag.ExitOnError) 13 | zero := command.Bool("z, zero", false, "end each output line with NUL, not newline") 14 | verion := command.Bool("version", false, "output version information and exit") 15 | 16 | command.Parse(argv[1:]) 17 | 18 | if *verion { 19 | fmt.Printf("todo output version \n") 20 | os.Exit(0) 21 | } 22 | 23 | args := command.Args() 24 | out := bytes.Buffer{} 25 | 26 | for _, v := range args { 27 | if len(v) > 1 && v[len(v)-1] == '/' || v[len(v)-1] == '\\' { 28 | v = v[:len(v)-1] 29 | } 30 | 31 | newDir := filepath.Dir(v) 32 | 33 | out.Write([]byte(newDir)) 34 | 35 | if *zero { 36 | out.WriteByte(0) 37 | } else { 38 | out.WriteByte('\n') 39 | } 40 | } 41 | 42 | os.Stdout.Write(out.Bytes()) 43 | } 44 | -------------------------------------------------------------------------------- /dirname/dirname/dirname.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/guonaihong/coreutils/dirname" 5 | "os" 6 | ) 7 | 8 | func main() { 9 | dirname.Main(os.Args) 10 | } 11 | -------------------------------------------------------------------------------- /echo/README.md: -------------------------------------------------------------------------------- 1 | # echo 2 | 3 | #### summary 4 | echo 5 | 6 | #### install 7 | ``` 8 | env GOPATH=`pwd` go get -u github.com/guonaihong/coreutils/echo/echo 9 | ``` 10 | 11 | #### usage 12 | ``` 13 | Usage of echo: 14 | -E disable interpretation of backslash escapes (default) (default true) 15 | -e enable interpretation of backslash escapes 16 | -n do not output the trailing newline 17 | ``` 18 | -------------------------------------------------------------------------------- /echo/echo.go: -------------------------------------------------------------------------------- 1 | package echo 2 | 3 | import ( 4 | "github.com/guonaihong/coreutils/utils" 5 | "github.com/guonaihong/flag" 6 | "io" 7 | "os" 8 | "strconv" 9 | ) 10 | 11 | type Echo struct { 12 | NewLine *bool 13 | Enable *bool 14 | Disable *bool 15 | } 16 | 17 | func New(argv []string) (*Echo, []string) { 18 | 19 | e := Echo{} 20 | 21 | command := flag.NewFlagSet(argv[0], flag.ExitOnError) 22 | 23 | e.NewLine = command.Opt("n", "do not output the trailing newline"). 24 | Flags(flag.PosixShort).NewBool(false) 25 | 26 | e.Enable = command.Opt("e", "enable interpretation of backslash escapes"). 27 | Flags(flag.PosixShort).NewBool(false) 28 | 29 | e.Disable = command.Opt("E", "disable interpretation of backslash escapes (default)"). 30 | Flags(flag.PosixShort).NewBool(true) 31 | 32 | command.Parse(argv[1:]) 33 | 34 | args := command.Args() 35 | 36 | return &e, args 37 | } 38 | 39 | func (e *Echo) Echo(args []string, w io.Writer) { 40 | 41 | c0 := uint64(0) 42 | var err error 43 | 44 | defer func() { 45 | if e.NewLine != nil && *e.NewLine == false { 46 | w.Write([]byte{'\n'}) 47 | } 48 | }() 49 | 50 | if e.Enable != nil && *e.Enable { 51 | 52 | printSlash := false 53 | for k, s := range args { 54 | for i := 0; i < len(s); i++ { 55 | c := s[i] 56 | 57 | if c == '\\' && i < len(s) { 58 | i++ 59 | if i >= len(s) { 60 | w.Write([]byte{'\\'}) 61 | goto notAnEscape 62 | } 63 | 64 | c = s[i] 65 | switch c { 66 | case 'a': 67 | c = '\a' 68 | case 'b': 69 | c = '\b' 70 | case 'c': 71 | return 72 | case 'e': 73 | c = '\x1B' 74 | case 'f': 75 | c = '\f' 76 | case 'n': 77 | c = '\n' 78 | case 'r': 79 | c = '\r' 80 | case 't': 81 | c = '\t' 82 | case 'v': 83 | c = '\v' 84 | case 'x': 85 | if i+1 >= len(s) { 86 | printSlash = true 87 | goto notAnEscape 88 | } 89 | 90 | n, haveHex := utils.IsXdigitStr(s[i+1:], 2) 91 | if !haveHex { 92 | printSlash = true 93 | goto notAnEscape 94 | } 95 | 96 | c0, err = strconv.ParseUint(s[i+1:i+1+n], 16, 32) 97 | if err != nil { 98 | printSlash = true 99 | goto notAnEscape 100 | } 101 | 102 | i = i + 1 + n - 1 103 | c = byte(c0) 104 | 105 | case '0': 106 | if i+1 >= len(s) { 107 | printSlash = true 108 | goto notAnEscape 109 | } 110 | 111 | n, haveOctal := utils.IsOctalStr(s[i+1:], 3) 112 | if !haveOctal { 113 | printSlash = true 114 | goto notAnEscape 115 | } 116 | 117 | c0, err = strconv.ParseUint(s[i+1:i+1+n], 8, 32) 118 | if err != nil { 119 | printSlash = true 120 | goto notAnEscape 121 | } 122 | 123 | i = i + 1 + n - 1 124 | c = byte(c0) 125 | case '\\': 126 | default: 127 | w.Write([]byte{'\\'}) 128 | } 129 | 130 | } 131 | 132 | notAnEscape: 133 | if printSlash { 134 | w.Write([]byte{'\\'}) 135 | printSlash = false 136 | } 137 | 138 | // fmt.Printf("%c") is not the same as the putchar output in c 139 | // in go fmt.Printf("%c\n", 172) --> ¬ 140 | // in c putchar(172) --> ? 141 | w.Write([]byte{c}) 142 | } 143 | if k+1 != len(args) { 144 | w.Write([]byte{' '}) 145 | } 146 | } 147 | return 148 | } 149 | 150 | if e.Disable == nil || *e.Disable { 151 | for i, s := range args { 152 | w.Write([]byte(s)) 153 | if i+1 != len(args) { 154 | w.Write([]byte{' '}) 155 | } 156 | } 157 | } 158 | 159 | } 160 | 161 | func Main(argv []string) { 162 | echo, args := New(argv) 163 | echo.Echo(args, os.Stdout) 164 | } 165 | -------------------------------------------------------------------------------- /echo/echo/echo.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/guonaihong/coreutils/echo" 5 | "os" 6 | ) 7 | 8 | func main() { 9 | echo.Main(os.Args) 10 | } 11 | -------------------------------------------------------------------------------- /echo/echo_test.go: -------------------------------------------------------------------------------- 1 | package echo 2 | 3 | import ( 4 | "bytes" 5 | "github.com/guonaihong/coreutils/utils" 6 | "testing" 7 | ) 8 | 9 | func testOptione(in []string, dst string, t *testing.T) { 10 | echo := Echo{} 11 | echo.Enable = utils.Bool(true) 12 | echo.NewLine = utils.Bool(true) 13 | w := &bytes.Buffer{} 14 | 15 | echo.Echo(in, w) 16 | 17 | if dst != w.String() { 18 | t.Fatalf("echo -e fail(%s,%x l:%d) need(%s, l:%d)\n", 19 | w.String(), w.String(), w.Len(), dst, len(dst)) 20 | } 21 | } 22 | 23 | // echo -e 24 | func TestOptione(t *testing.T) { 25 | 26 | in := []string{ 27 | `\n\r`, 28 | } 29 | 30 | testOptione(in, "\n\r", t) 31 | in = []string{ 32 | "hello", 33 | "world", 34 | } 35 | 36 | testOptione(in, "hello world", t) 37 | 38 | in = []string{ 39 | `\a\b\f\n\r\t\v\000\xff`, 40 | } 41 | 42 | //todo 43 | /* 44 | \e escape 45 | */ 46 | testOptione(in, "\a\b\f\n\r\t\v\000\xff", t) 47 | 48 | in = []string{ 49 | `\e`, 50 | } 51 | testOptione(in, "\x1b", t) 52 | } 53 | 54 | func testOptionn(in []string, dst string, t *testing.T) { 55 | echo := Echo{} 56 | echo.NewLine = utils.Bool(true) 57 | w := &bytes.Buffer{} 58 | 59 | echo.Echo(in, w) 60 | 61 | if dst != w.String() { 62 | t.Fatalf("echo -n fail(%s,%x l:%d) need(%s, l:%d)\n", 63 | w.String(), w.String(), w.Len(), dst, len(dst)) 64 | } 65 | } 66 | 67 | // echo -n 68 | func TestOptionn(t *testing.T) { 69 | in := []string{ 70 | "hello", "world", "12345", 71 | } 72 | 73 | testOptionn(in, "hello world 12345", t) 74 | } 75 | 76 | func testOptionE(in []string, dst string, t *testing.T) { 77 | echo := Echo{} 78 | echo.Disable = utils.Bool(true) 79 | w := &bytes.Buffer{} 80 | 81 | echo.Echo(in, w) 82 | 83 | if dst != w.String() { 84 | t.Fatalf("echo -E fail(%s,%x l:%d) need(%s, l:%d)\n", 85 | w.String(), w.String(), w.Len(), dst, len(dst)) 86 | } 87 | } 88 | 89 | // echo -E 90 | func TestOptionE(t *testing.T) { 91 | in := []string{ 92 | "hello", `\n\n\n\n`, 93 | } 94 | 95 | testOptionn(in, `hello \n\n\n\n`, t) 96 | } 97 | -------------------------------------------------------------------------------- /env/env.go: -------------------------------------------------------------------------------- 1 | package env 2 | 3 | import ( 4 | "fmt" 5 | "github.com/guonaihong/flag" 6 | "os" 7 | "os/exec" 8 | "strings" 9 | ) 10 | 11 | type Env struct { 12 | IgnoreEnvironment bool `opt:"i, ignore-environment" usage:"start with an empty environment"` 13 | Unset string `opt:"u, unset" usage:"remove variable from the environment"` 14 | //Chdir string `opt:"C, chdir" usage:"change working directory to DIR"` 15 | Null bool `opt:"0, null" usage:"end each output line with NUL, not newline"` 16 | } 17 | 18 | func Main(argv []string) { 19 | var env Env 20 | delimiter := byte('\n') 21 | 22 | command := flag.NewFlagSet(argv[0], flag.ExitOnError) 23 | command.ParseStruct(argv[1:], &env) 24 | args := command.Args() 25 | 26 | if env.IgnoreEnvironment { 27 | os.Clearenv() 28 | } 29 | 30 | if env.Null { 31 | delimiter = byte(0) 32 | } 33 | 34 | for k, v := range args { 35 | if strings.IndexByte(v, '=') > 0 { 36 | kv := strings.SplitN(v, "=", 2) 37 | if err := os.Setenv(kv[0], kv[1]); err != nil { 38 | fmt.Printf("%s\n", err) 39 | } 40 | continue 41 | } 42 | 43 | cmd := exec.Command(args[k], args[k+1:]...) 44 | cmd.Stdout = os.Stdout 45 | cmd.Stdin = os.Stdin 46 | cmd.Stderr = os.Stderr 47 | err := cmd.Run() 48 | if err != nil { 49 | fmt.Printf("%s\n", err) 50 | os.Exit(1) 51 | } 52 | 53 | os.Exit(0) 54 | } 55 | 56 | allArgs := os.Environ() 57 | for _, v := range allArgs { 58 | fmt.Fprintf(os.Stdout, "%s%c", v, delimiter) 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /hashcore/hashcore.go: -------------------------------------------------------------------------------- 1 | package hashcore 2 | 3 | import ( 4 | "bufio" 5 | "bytes" 6 | "crypto/md5" 7 | "crypto/sha1" 8 | "crypto/sha256" 9 | "crypto/sha512" 10 | "errors" 11 | "fmt" 12 | "github.com/guonaihong/coreutils/utils" 13 | "github.com/guonaihong/flag" 14 | "hash" 15 | "io" 16 | "os" 17 | "strings" 18 | ) 19 | 20 | type Type int 21 | 22 | const ( 23 | Md5 Type = iota 24 | Sha1 25 | Sha256 26 | Sha224 27 | Sha384 28 | Sha512 29 | ) 30 | 31 | func (t Type) String() string { 32 | switch t { 33 | case Md5: 34 | return "MD5" 35 | case Sha1: 36 | return "SHA1" 37 | case Sha256: 38 | return "SHA256" 39 | case Sha224: 40 | return "SHA224" 41 | case Sha384: 42 | return "SHA384" 43 | case Sha512: 44 | return "SHA512" 45 | } 46 | 47 | panic("unkown") 48 | } 49 | 50 | type HashCore struct { 51 | Binary *bool 52 | Check *string 53 | Tag *bool 54 | Text *bool 55 | IgnoreMissing *bool 56 | Quiet *bool 57 | Status *bool 58 | Strict *bool 59 | Warn *bool 60 | hash hash.Hash 61 | } 62 | 63 | func New(argv []string, hashName string, t Type) (*HashCore, []string) { 64 | hash := &HashCore{} 65 | 66 | command := flag.NewFlagSet(argv[0], flag.ExitOnError) 67 | 68 | hash.Binary = command.Opt("b, binary", "read in binary mode"). 69 | Flags(flag.PosixShort).NewBool(false) 70 | 71 | hash.Check = command.Opt("c, check", fmt.Sprintf("read %s sums from the FILEs and check them", t)). 72 | Flags(flag.PosixShort).NewString("") 73 | 74 | hash.Tag = command.Opt("tag", "create a BSD-style checksum"). 75 | Flags(flag.PosixShort).NewBool(false) 76 | 77 | hash.IgnoreMissing = command.Opt("ignore-missing", "don't fail or report status for missing files"). 78 | Flags(flag.PosixShort).NewBool(false) 79 | 80 | hash.Quiet = command.Opt("quiet", "don't print OK for each successfully verified file"). 81 | Flags(flag.PosixShort).NewBool(false) 82 | 83 | hash.Status = command.Opt("status", "don't output anything, status code shows success"). 84 | Flags(flag.PosixShort).NewBool(false) 85 | 86 | hash.Strict = command.Opt("strict", "exit non-zero for improperly formatted checksum lines"). 87 | Flags(flag.PosixShort).NewBool(false) 88 | 89 | hash.Warn = command.Opt("w, warn", "warn about improperly formatted checksum lines"). 90 | Flags(flag.PosixShort).NewBool(false) 91 | 92 | command.Parse(argv[1:]) 93 | 94 | return hash, command.Args() 95 | } 96 | 97 | func (h *HashCore) formatError(t Type, fileName string, err error) string { 98 | switch e := err.(type) { 99 | case *os.PathError: 100 | err = e.Err 101 | if os.IsNotExist(err) { 102 | 103 | return fmt.Sprintf("%ssum: %s: No such file or directory\n", 104 | strings.ToLower(t.String()), fileName) 105 | } 106 | } 107 | return err.Error() 108 | } 109 | 110 | func (h *HashCore) CheckHash(t Type, formatFail *int, fileName string, w io.Writer) error { 111 | 112 | fd, err := utils.OpenFile(fileName) 113 | if err != nil { 114 | if h.IsStatus() { 115 | os.Exit(1) 116 | } 117 | return errors.New(h.formatError(t, fileName, err)) 118 | } 119 | 120 | defer fd.Close() 121 | 122 | br := bufio.NewReader(fd) 123 | 124 | var out bytes.Buffer 125 | 126 | var checkFail, readFail, done int 127 | 128 | hashName := strings.ToLower(t.String()) 129 | defer func(f, c, read, done *int) { 130 | if *f > 0 { 131 | fmt.Fprintf(w, "%ssum: WARNING: %d line is improperly formatted\n", 132 | hashName, *f) 133 | } 134 | 135 | if *c > 0 { 136 | fmt.Fprintf(w, "%ssum: WARNING: %d computed checksums did NOT match\n", 137 | hashName, *c) 138 | } 139 | 140 | if *read > 0 { 141 | fmt.Fprintf(w, "%ssum: WARNING: %d listed file could not be read\n", 142 | hashName, *read) 143 | } 144 | 145 | if h.IsIgnoreMissing() && *done == 0 { 146 | fmt.Fprintf(w, "%ssum: %s: no file was verified\n", 147 | hashName, fileName) 148 | } 149 | }(formatFail, &checkFail, &readFail, &done) 150 | 151 | for no := 1; ; no++ { 152 | 153 | l, e := br.ReadBytes('\n') 154 | 155 | if e != nil && len(l) == 0 { 156 | break 157 | } 158 | 159 | hashAndFile := bytes.Fields(l) 160 | 161 | var fileName string 162 | 163 | switch { 164 | case len(hashAndFile) == 2: 165 | fileName = string(hashAndFile[1]) 166 | h.Binary = utils.Bool(false) 167 | if len(fileName) > 0 && fileName[0] == '*' { 168 | fileName = fileName[1:] 169 | h.Binary = utils.Bool(true) 170 | } 171 | case len(hashAndFile) == 3: 172 | _, err := fmt.Sscanf(string(hashAndFile[1]), "(%s)", &fileName) 173 | if err != nil { 174 | return err 175 | } 176 | 177 | if h.Warn != nil && *h.Warn { 178 | fmt.Fprintf(w, "%sum: %s: %d: improperly formatted MD5 checksum line\n", 179 | hashName, fileName, no) 180 | } 181 | default: 182 | (*formatFail)++ 183 | } 184 | 185 | err := h.Hash(t, fileName, &out) 186 | if err != nil { 187 | if !h.IsIgnoreMissing() { 188 | fmt.Fprintf(w, h.formatError(t, fileName, err)) 189 | fmt.Fprintf(w, "%s: FAILED open or read\n", fileName) 190 | readFail++ 191 | } 192 | if h.IsStatus() { 193 | os.Exit(1) 194 | } 195 | continue 196 | } 197 | 198 | if bytes.Equal(out.Bytes(), l) { 199 | if !h.IsQuiet() { 200 | fmt.Fprintf(w, "%s: OK\n", fileName) 201 | } 202 | done++ 203 | } else { 204 | if !h.IsIgnoreMissing() { 205 | fmt.Fprintf(w, "%s: FAILED\n", fileName) 206 | } 207 | } 208 | } 209 | return nil 210 | } 211 | 212 | func (h *HashCore) IsStatus() bool { 213 | return h.Status != nil && *h.Status 214 | } 215 | 216 | func (h *HashCore) IsIgnoreMissing() bool { 217 | return h.IgnoreMissing != nil && *h.IgnoreMissing 218 | } 219 | 220 | func (h *HashCore) IsQuiet() bool { 221 | return h.Quiet != nil && *h.Quiet 222 | } 223 | 224 | func (h *HashCore) IsCheck() bool { 225 | return h.Check != nil && len(*h.Check) > 0 226 | } 227 | 228 | func (h *HashCore) IsTag() bool { 229 | return h.Tag != nil && *h.Tag 230 | } 231 | 232 | func (h *HashCore) Hash(t Type, fileName string, w io.Writer) error { 233 | 234 | asterisk := " " 235 | if h.Binary != nil && *h.Binary { 236 | asterisk = " *" 237 | } 238 | 239 | fd, err := utils.OpenFile(fileName) 240 | if err != nil { 241 | return err 242 | } 243 | defer fd.Close() 244 | 245 | switch t { 246 | case Md5: 247 | h.hash = md5.New() 248 | case Sha1: 249 | h.hash = sha1.New() 250 | case Sha224: 251 | h.hash = sha256.New224() 252 | case Sha256: 253 | h.hash = sha256.New() 254 | case Sha384: 255 | h.hash = sha512.New384() 256 | case Sha512: 257 | h.hash = sha512.New() 258 | } 259 | 260 | io.Copy(h.hash, fd) 261 | 262 | if !h.IsTag() { 263 | fmt.Fprintf(w, "%x%s%s\n", h.hash.Sum(nil), asterisk, fileName) 264 | } else { 265 | fmt.Fprintf(w, "%s (%s) = %x\n", t, fileName, h.hash.Sum(nil)) 266 | } 267 | 268 | return nil 269 | } 270 | 271 | func Main(argv []string, t Type) { 272 | hash, args := New(argv, t.String(), t) 273 | if len(args) == 0 { 274 | args = append(args, "-") 275 | } 276 | 277 | var isFormatFail bool 278 | for _, fileName := range args { 279 | var err error 280 | var formatFail int 281 | 282 | if hash.IsCheck() { 283 | err = hash.CheckHash(t, &formatFail, fileName, os.Stdout) 284 | isFormatFail = formatFail > 0 285 | } else { 286 | hash.Hash(t, fileName, os.Stdout) 287 | } 288 | 289 | if err != nil { 290 | os.Stdout.Write([]byte(err.Error())) 291 | } 292 | } 293 | 294 | if hash.Strict != nil && *hash.Strict { 295 | if isFormatFail { 296 | os.Exit(1) 297 | } 298 | } 299 | } 300 | -------------------------------------------------------------------------------- /hashcore/hashcore_test.go: -------------------------------------------------------------------------------- 1 | package hashcore 2 | 3 | import ( 4 | "github.com/guonaihong/coreutils/utils" 5 | "strings" 6 | "testing" 7 | ) 8 | 9 | const errMsgFileDoesNotExist = `md5sum: not.sum: No such file or directory 10 | md5sum: kao: No such file or directory 11 | kao: FAILED open or read 12 | md5sum: WARNING: 1 listed file could not be read 13 | ` 14 | const okMsg = `md5sum.sum: OK 15 | ` 16 | 17 | const errMsgIgnoreMissing = `md5sum: not.sum: No such file or directory 18 | md5sum: md5sum.sum: no file was verified 19 | ` 20 | 21 | func testHashCheck(hashType Type, need string, fileNames []string, quiet bool, ignoreMissing bool, t *testing.T) { 22 | var s strings.Builder 23 | var formatFail int 24 | h := HashCore{} 25 | h.Quiet = utils.Bool(quiet) 26 | h.IgnoreMissing = utils.Bool(ignoreMissing) 27 | for _, f := range fileNames { 28 | err := h.CheckHash(hashType, &formatFail, f, &s) 29 | if err != nil { 30 | s.WriteString(err.Error()) 31 | } 32 | } 33 | 34 | if need != s.String() { 35 | t.Errorf("need(%s) actual(%s)\n", need, s.String()) 36 | } 37 | } 38 | 39 | func TestHashCheckFileDoesNotExist(t *testing.T) { 40 | testHashCheck(Md5, errMsgFileDoesNotExist, []string{"not.sum", "md5sum.sum"}, false, false, t) 41 | testHashCheck(Md5, errMsgFileDoesNotExist, []string{"not.sum", "md5sum.sum"}, true, false, t) 42 | testHashCheck(Md5, errMsgIgnoreMissing, []string{"not.sum", "md5sum.sum"}, false, true, t) 43 | testHashCheck(Md5, errMsgIgnoreMissing, []string{"not.sum", "md5sum.sum"}, true, true, t) 44 | } 45 | 46 | func TestHashFileExist2(t *testing.T) { 47 | testHashCheck(Md5, okMsg, []string{"ok.md5.sum"}, false, false, t) 48 | testHashCheck(Md5, "", []string{"ok.md5.sum"}, true, false, t) 49 | } 50 | 51 | func TestHashFileExist1(t *testing.T) { 52 | testHashCheck(Md5, okMsg, []string{"ok.binary.md5.sum"}, false, false, t) 53 | testHashCheck(Md5, "", []string{"ok.md5.sum"}, true, false, t) 54 | } 55 | -------------------------------------------------------------------------------- /hashcore/md5sum.sum: -------------------------------------------------------------------------------- 1 | 21c93af41e088f56a009666a90ff82b4 kao 2 | -------------------------------------------------------------------------------- /hashcore/md5sum2.sum: -------------------------------------------------------------------------------- 1 | 21c93af41e088f56a009666a90ff82b4 2 | 21c93af41e088f56a009666a90ff82b4 kao 3 | 21c93af41e088f56a009666a90ff82b4 4 | 21c93af41e088f56a009666a90ff82b4 5 | 21c93af41e088f56a009666a90ff82b4 6 | 21c93af41e088f56a009666a90ff82b4 7 | 21c93af41e088f56a009666a90ff82b4 kao 8 | -------------------------------------------------------------------------------- /hashcore/ok.binary.md5.sum: -------------------------------------------------------------------------------- 1 | 24ba1832ae72a76c29dc8be4c0dc8508 *md5sum.sum 2 | -------------------------------------------------------------------------------- /hashcore/ok.md5.sum: -------------------------------------------------------------------------------- 1 | 24ba1832ae72a76c29dc8be4c0dc8508 md5sum.sum 2 | -------------------------------------------------------------------------------- /head/README.md: -------------------------------------------------------------------------------- 1 | # head 2 | 3 | #### summary 4 | head has the same head command as ubuntu 18.04 5 | 6 | #### install 7 | ``` 8 | env GOPATH=`pwd` go get -u github.com/guonaihong/coreutils/head/head 9 | ``` 10 | 11 | #### usage 12 | ```console 13 | Usage of head: 14 | -^\d+$, --n, --lines int 15 | print the first NUM lines instead of the first 10; 16 | with the leading '-', print all but the last 17 | NUM lines of each file 18 | -c, --bytes string 19 | print the first NUM bytes of each file; 20 | with the leading '-', print all but the last 21 | NUM bytes of each file 22 | -q, --quiet, --silent 23 | never print headers giving file names 24 | -v, --verbose 25 | always print headers giving file names 26 | -z, --zero-terminated 27 | line delimiter is NUL, not newline 28 | ``` 29 | -------------------------------------------------------------------------------- /head/head.go: -------------------------------------------------------------------------------- 1 | package head 2 | 3 | import ( 4 | "bufio" 5 | "fmt" 6 | "github.com/guonaihong/coreutils/utils" 7 | "github.com/guonaihong/flag" 8 | "io" 9 | "os" 10 | ) 11 | 12 | type Head struct { 13 | Bytes *int 14 | Lines *int 15 | Quiet *bool 16 | Verbose *bool 17 | LineDelim byte 18 | } 19 | 20 | func New(argv []string) (*Head, []string) { 21 | command := flag.NewFlagSet(argv[0], flag.ExitOnError) 22 | 23 | h := Head{} 24 | 25 | nbytes := command.Opt("c, bytes", "print the first NUM bytes of each file;\n"+ 26 | " with the leading '-', print all but the last\nNUM bytes of each file"). 27 | Flags(flag.PosixShort). 28 | NewString("0") 29 | 30 | h.Lines = command.OptOpt( 31 | flag.Flag{ 32 | Regex: `^\d+$`, 33 | Short: []string{"n"}, 34 | Long: []string{"lines"}, 35 | Usage: "print the first NUM lines instead of the first 10;\n" + 36 | "with the leading '-', print all but the last\n" + 37 | "NUM lines of each file"}). 38 | Flags(flag.RegexKeyIsValue | flag.PosixShort). 39 | NewInt(10) 40 | 41 | h.Quiet = command.Opt("q, quiet, silent", "never print headers giving file names"). 42 | Flags(flag.PosixShort). 43 | NewBool(false) 44 | 45 | h.Verbose = command.Opt("v, verbose", "always print headers giving file names"). 46 | Flags(flag.PosixShort). 47 | NewBool(false) 48 | 49 | zeroTerminated := command.Opt("z, zero-terminated", "line delimiter is NUL, not newline"). 50 | Flags(flag.PosixShort). 51 | NewBool(false) 52 | 53 | command.Parse(argv[1:]) 54 | 55 | n, err := utils.HeadParseSize(*nbytes) 56 | if err != nil { 57 | utils.Die("head:%s\n", err) 58 | } 59 | 60 | h.Bytes = n.IntPtr() 61 | 62 | h.LineDelim = '\n' 63 | if *zeroTerminated { 64 | h.LineDelim = '\000' 65 | } 66 | 67 | args := command.Args() 68 | if len(args) == 0 { 69 | args = append(args, "-") 70 | } 71 | 72 | return &h, args 73 | } 74 | 75 | func (h *Head) PrintBytes(rs io.ReadSeeker, w io.Writer) { 76 | size := *h.Bytes 77 | buf := make([]byte, 1024*8) 78 | 79 | if size < 0 { 80 | if n, err := rs.Seek(0, 2); err != nil { 81 | utils.Die("head: %s\n", err) 82 | return 83 | } else { 84 | size = int(n) + *h.Bytes 85 | rs.Seek(0, 0) 86 | } 87 | 88 | } 89 | 90 | for size > 0 { 91 | needRead := len(buf) 92 | if needRead > size { 93 | needRead = size 94 | } 95 | n, err := rs.Read(buf[:needRead]) 96 | if err == io.EOF { 97 | break 98 | } 99 | 100 | w.Write(buf[:n]) 101 | size -= n 102 | } 103 | } 104 | 105 | func (h *Head) PrintLines(rs io.ReadSeeker, w io.Writer) { 106 | br := bufio.NewReader(rs) 107 | 108 | lineNo := 10 109 | if h.Lines != nil { 110 | lineNo = *h.Lines 111 | } 112 | 113 | if lineNo < 0 { 114 | no := 0 115 | for no = 0; ; no++ { 116 | l, e := br.ReadBytes(h.LineDelim) 117 | if e != nil && len(l) == 0 { 118 | break 119 | } 120 | 121 | } 122 | 123 | rs.Seek(0, 0) 124 | lineNo = no + lineNo 125 | } 126 | 127 | for i := 0; i < lineNo; i++ { 128 | l, e := br.ReadBytes(h.LineDelim) 129 | if e != nil && len(l) == 0 { 130 | break 131 | } 132 | 133 | w.Write(l) 134 | } 135 | } 136 | 137 | func (h *Head) main(rs io.ReadSeeker, w io.Writer, name string) { 138 | if h.Verbose != nil && *h.Verbose { 139 | h.PrintTitle(w, name) 140 | } 141 | 142 | if h.Bytes != nil && *h.Bytes != 0 { 143 | h.PrintBytes(rs, w) 144 | return 145 | } 146 | 147 | h.PrintLines(rs, w) 148 | } 149 | 150 | func (h *Head) PrintTitle(w io.Writer, name string) { 151 | fmt.Fprintf(w, "==> %s <==\n", name) 152 | } 153 | 154 | func Main(argv []string) { 155 | 156 | h, args := New(argv) 157 | 158 | for _, v := range args { 159 | fd, err := utils.OpenFile(v) 160 | if err != nil { 161 | utils.Die("head:%s\n", err) 162 | } 163 | 164 | h.main(fd, os.Stdout, v) 165 | fd.Close() 166 | } 167 | } 168 | -------------------------------------------------------------------------------- /head/head/head.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/guonaihong/coreutils/head" 5 | "os" 6 | ) 7 | 8 | func main() { 9 | head.Main(os.Args) 10 | } 11 | -------------------------------------------------------------------------------- /head/head_test.go: -------------------------------------------------------------------------------- 1 | package head 2 | 3 | import ( 4 | "bytes" 5 | "strings" 6 | "testing" 7 | ) 8 | 9 | func TestCommandOption(t *testing.T) { 10 | h, _ := New([]string{"head", "-3", "./wc.go"}) 11 | if h.Lines == nil || *h.Lines != 3 { 12 | t.Error("Command line parsing error, the required value is -3") 13 | } 14 | 15 | h, _ = New([]string{"head", "-n", "4", "./wc.go"}) 16 | if h.Lines == nil || *h.Lines != 4 { 17 | t.Error("Command line parsing error, the required value is -n 4") 18 | } 19 | } 20 | 21 | func TestPrintBytes(t *testing.T) { 22 | //test head -c 3 23 | h := Head{} 24 | v := 3 25 | h.Bytes = &v 26 | 27 | str := "123456789" 28 | rs := strings.NewReader(str) 29 | w := &bytes.Buffer{} 30 | 31 | h.PrintBytes(rs, w) 32 | if w.String() != "123" { 33 | t.Errorf("need to be 123:(%s)\n", w.String()) 34 | } 35 | 36 | //test head -c -3 37 | v = -3 38 | h.Bytes = &v 39 | 40 | rs = strings.NewReader(str) 41 | w = &bytes.Buffer{} 42 | 43 | h.PrintBytes(rs, w) 44 | if w.String() != "123456" { 45 | t.Errorf("need to be 789:(%s)\n", w.String()) 46 | } 47 | 48 | //test head -c 3 49 | src := append([]byte("abc"), make([]byte, 1024*8)...) 50 | src = append(src, []byte("def")...) 51 | 52 | v = 3 53 | h.Bytes = &v 54 | 55 | brs := bytes.NewReader(src) 56 | w = &bytes.Buffer{} 57 | 58 | h.PrintBytes(brs, w) 59 | if w.String() != "abc" { 60 | t.Errorf("need to be abc:(%s)\n", w.String()) 61 | } 62 | 63 | //test head -c 0 64 | v = 0 65 | h.Bytes = &v 66 | brs = bytes.NewReader(src) 67 | w = &bytes.Buffer{} 68 | 69 | h.PrintBytes(brs, w) 70 | if w.String() != "" { 71 | t.Errorf("need to be \"\":(%s)\n", w.String()) 72 | } 73 | } 74 | 75 | func TestPrintLines(t *testing.T) { 76 | h := Head{LineDelim: '\n'} 77 | 78 | h10 := `1 79 | 2 80 | 3 81 | 4 82 | 5 83 | 6 84 | 7 85 | 8 86 | 9 87 | 10` 88 | 89 | rs := strings.NewReader(h10) 90 | w := &bytes.Buffer{} 91 | 92 | h.PrintLines(rs, w) 93 | if w.String() != h10 { 94 | t.Errorf("need to be \n(%s):\n(%s)\n", h10, w.String()) 95 | } 96 | 97 | aZ := `a 98 | b 99 | c 100 | d 101 | ` 102 | last := `e` 103 | 104 | lines := -1 105 | h.Lines = &lines 106 | 107 | rs = strings.NewReader(aZ + last) 108 | w.Reset() 109 | 110 | h.PrintLines(rs, w) 111 | if w.String() != aZ { 112 | t.Errorf("need to be \n(%s):\n(%s)\n", aZ, w.String()) 113 | } 114 | 115 | } 116 | 117 | func TestPrintTitle(t *testing.T) { 118 | verbose := true 119 | h := Head{Verbose: &verbose} 120 | w := &bytes.Buffer{} 121 | 122 | h.PrintTitle(w, "test.go") 123 | 124 | needStr := "==> test.go <==\n" 125 | if w.String() != needStr { 126 | t.Errorf("need to be (%s):(%s)\n", needStr, w.String()) 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /join/README.md: -------------------------------------------------------------------------------- 1 | # join 2 | 3 | #### 简介 4 | join 5 | 6 | #### install 7 | ``` 8 | env GOPATH=`pwd` go get -u github.com/guonaihong/coreutils/join 9 | ``` 10 | 11 | #### example 12 | (下面的) 13 | (下面的内容来自网络) 14 | join用于连接公共字段的两个文件的行。默认连接字段是由空格分隔的第一个字段。 15 | 请看下面两个文件foodtypes.txt和foods.txt 16 | ``` 17 | cat foodtypes.txt 18 | 1 Protein 19 | 2 Carbohydrate 20 | 3 Fat 21 | 22 | cat foods.txt 23 | 1 Cheese 24 | 2 Potato 25 | 3 Butter 26 | ``` 27 | 28 | 这两个文件共享第一个字段,所以可以连接 29 | ``` 30 | join foodtypes foods.txt 31 | 1 Protein Cheese 32 | 2 Carbohydrate Potato 33 | 3 Fat Butter 34 | ``` 35 | 要使用不同的字段连接文件,可以传递-1和-2选项以进行连接。 在以下示例中,有两个文件wine.txt和reviews.txt。 36 | ``` 37 | cat wine.txt 38 | Red Beaunes France 39 | White Reisling Germany 40 | Red Riocha Spain 41 | 42 | cat reviews.txt 43 | Beaunes Great! 44 | Reisling Terrible! 45 | Riocha Meh 46 | ``` 47 | 可以通过指定应该用于加入文件的字段来连接这些文件。 这两个文件的共同点是葡萄酒的名称。 48 | 在wine.txt中,这是第二个字段。 在reviews.txt中,这是第一个字段。 49 | 通过指定这些字段,可以使用-1和-2连接文件。 50 | 51 | 在这些文件上运行联接会导致错误,因为文件未排序。 52 | ``` 53 | join -1 2 -2 1 wine.txt reviews.txt 54 | join: wine.txt:3: is not sorted: Red Beaunes France 55 | join: reviews.txt:2: is not sorted: Beaunes Great! 56 | Riocha Red Spain Meh 57 | Beaunes Red France Great! 58 | ``` 59 | 用sort命令排序之后再join 60 | ``` 61 | join -1 2 -2 1 <(sort -k 2 wine.txt) <(sort reviews.txt) 62 | Beaunes Red France Great! 63 | Reisling White Germany Terrible! 64 | Riocha Red Spain Meh 65 | ``` 66 | 67 | 如何指定分割符 68 | 要使用join指定分割符,可以使用-t选项。特别是join csv文件 69 | ``` 70 | cat names.csv 71 | 1,John Smith,London 72 | 2,Arthur Dent, Newcastle 73 | 3,Sophie Smith,London 74 | 75 | cat transactions.csv 76 | £1234,Deposit,John Smith 77 | £4534,Withdrawal,Arthur Dent 78 | £4675,Deposit,Sophie Smith 79 | ``` 80 | 使用-t指定分割符 81 | ``` 82 | join -1 2 -2 3 -t , names.csv transactions.csv 83 | John Smith,1,London,£1234,Deposit 84 | Arthur Dent,2, Newcastle,£4534,Withdrawal 85 | Sophie Smith,3,London,£4675,Deposit 86 | ``` 87 | ``` 88 | 259/5000 89 | 如何指定输出格式 90 | 要指定join的输出格式,请使用-o选项。 这允许定义将在输出中显示的字段的顺序,或仅显示某些字段。 91 | 92 | 在前面的例子中输出我们如下。 93 | ``` 94 | John Smith,1,London,£1234,Deposit 95 | ``` 96 | 要指定顺序,将字段列表传递给-o。 对于这个例子,这是-o 1.1,1.2,1.3,2.2,2.1。 这将按所需顺序格式化输出。 97 | ``` 98 | join -1 2 -2 3 -t , -o 1.1,1.2,1.3,2.2,2.1 names.csv transactions.csv 99 | 1,John Smith,London,Deposit,£1234 100 | 2,Arthur Dent, Newcastle,Withdrawal,£4534 101 | 3,Sophie Smith,London,Deposit,£4675 102 | ``` 103 | #####参考资料 104 | * https://shapeshed.com/unix-join/ 105 | -------------------------------------------------------------------------------- /join/join.go: -------------------------------------------------------------------------------- 1 | package join 2 | 3 | import ( 4 | "bufio" 5 | "bytes" 6 | _ "fmt" 7 | "github.com/guonaihong/coreutils/utils" 8 | "github.com/guonaihong/flag" 9 | "io" 10 | ) 11 | 12 | var lineDelim byte = '\n' 13 | 14 | func delEndLineDelim(l *[]byte) { 15 | if len(*l) > 0 && (*l)[len(*l)-1] == lineDelim { 16 | *l = (*l)[:len(*l)-1] 17 | } 18 | } 19 | 20 | type joinCmd struct { 21 | key1 *int 22 | key2 *int 23 | printUnpairable *int 24 | separator *string 25 | ignoreCase *bool 26 | empty *string 27 | r1, r2 io.Reader 28 | w io.Writer 29 | } 30 | 31 | func (j *joinCmd) addOutLine(outLine *bytes.Buffer, ls [][]byte, key int, writeLastSeparator bool) { 32 | 33 | for k, v := range ls { 34 | if k == key { 35 | continue 36 | } 37 | 38 | outLine.Write(v) 39 | if !writeLastSeparator && k+1 == len(ls) { 40 | return 41 | } 42 | 43 | outLine.WriteString(*j.separator) 44 | } 45 | 46 | } 47 | 48 | func (j *joinCmd) getField(l1, l2 []byte) { 49 | delEndLineDelim(&l1) 50 | delEndLineDelim(&l2) 51 | split := bytes.Split 52 | 53 | var vals [2][]byte 54 | var lineWord [2][][]byte 55 | var keys [2]int 56 | 57 | lineWord[0] = split(l1, []byte(*j.separator)) 58 | lineWord[1] = split(l2, []byte(*j.separator)) 59 | 60 | outLine := bytes.NewBuffer(nil) 61 | 62 | /* 63 | isBytesIndex := func(index int, b []byte) bool { 64 | return index >= 0 && index < len(b) 65 | } 66 | */ 67 | 68 | isBytesBytesIndex := func(index int, bb [][]byte) bool { 69 | return index >= 0 && index < len(bb) 70 | } 71 | 72 | key1, key2 := *j.key1-1, *j.key2-1 73 | keys[0], keys[1] = key1, key2 74 | 75 | if isBytesBytesIndex(key1, lineWord[0]) { 76 | vals[0] = lineWord[0][key1] 77 | } 78 | 79 | if isBytesBytesIndex(key2, lineWord[1]) { 80 | vals[1] = lineWord[1][key2] 81 | } 82 | 83 | if *j.ignoreCase { 84 | vals[0], vals[1] = bytes.ToUpper(vals[0]), bytes.ToUpper(vals[1]) 85 | } 86 | 87 | printUnpairable := *j.printUnpairable - 1 88 | if bytes.Equal(vals[0], vals[1]) { 89 | outLine.Write(lineWord[0][key1]) 90 | outLine.WriteString(*j.separator) 91 | 92 | j.addOutLine(outLine, lineWord[0], key1, true) 93 | j.addOutLine(outLine, lineWord[1], key2, false) 94 | goto write 95 | } 96 | 97 | switch *j.printUnpairable { 98 | case 0, 1: 99 | if isBytesBytesIndex(keys[printUnpairable], lineWord[printUnpairable]) { 100 | outLine.Write(lineWord[printUnpairable][key1]) 101 | outLine.WriteString(*j.separator) 102 | j.addOutLine(outLine, lineWord[printUnpairable], keys[printUnpairable], false) 103 | } 104 | } 105 | 106 | write: 107 | outLine.WriteByte('\n') 108 | line := outLine.Bytes() 109 | if len(line) > 0 { 110 | j.w.Write(line) 111 | } 112 | } 113 | 114 | func (j *joinCmd) main() { 115 | br1 := bufio.NewReader(j.r1) 116 | br2 := bufio.NewReader(j.r2) 117 | 118 | fileEof1, fileEof2 := false, false 119 | var l1, l2 []byte 120 | var err error 121 | 122 | for { 123 | 124 | if !fileEof1 { 125 | l1, err = br1.ReadBytes(lineDelim) 126 | if err != nil && len(l1) == 0 { 127 | fileEof1 = true 128 | } 129 | } 130 | 131 | if !fileEof2 { 132 | l2, err = br2.ReadBytes(lineDelim) 133 | if err != nil && len(l2) == 0 { 134 | fileEof2 = true 135 | } 136 | } 137 | 138 | if fileEof1 && fileEof2 { 139 | break 140 | } 141 | 142 | j.getField(l1, l2) 143 | } 144 | } 145 | 146 | func Main(argv []string) { 147 | 148 | cmdOpt := joinCmd{} 149 | command := flag.NewFlagSet(argv[0], flag.ExitOnError) 150 | cmdOpt.printUnpairable = command.Int("a", 0, "also print unpairable lines from file FILENUM, "+ 151 | "where FILENUM is 1 or 2, corresponding to FILE1 or FILE2") 152 | cmdOpt.empty = command.String("e", "", "replace missing input fields with EMPTY") 153 | cmdOpt.ignoreCase = command.Bool("i, ignore-case", false, "ignore differences in case "+ 154 | "when comparing fields") 155 | command.Int("j", 0, "equivalent to '-1 FIELD -2 FIELD'") 156 | command.StringSlice("o", []string{}, "obey FORMAT while constructing output line") 157 | cmdOpt.separator = command.String("t", " ", "use CHAR as input and output field separator") 158 | command.String("v", "", "like -a FILENUM, but suppress joined output lines") 159 | cmdOpt.key1 = command.Int("1", 1, "join on this FIELD of file 1") 160 | cmdOpt.key2 = command.Int("2", 1, "join on this FIELD of file 2") 161 | command.Bool("check-order", false, "check that the input is correctly sorted, "+ 162 | "even if all input lines are pairable") 163 | command.Bool("nocheck-order", false, "do not check that the input is correctly sorted") 164 | command.Bool("header", false, "treat the first line in each file as field headers,"+ 165 | " print them without trying to pair them") 166 | command.Bool("z, zero-terminated", false, "line delimiter is NUL, not newline") 167 | 168 | command.Parse(argv[1:]) 169 | 170 | args := command.Args() 171 | if len(args) != 2 { 172 | if len(args) == 0 { 173 | utils.Die("uniq: missing operand \n") 174 | } else { 175 | utils.Die("uniq:missing operand after %s\n", args[len(args)]) 176 | } 177 | } 178 | 179 | fd1, err := utils.OpenInputFd(args[0]) 180 | if err != nil { 181 | utils.Die("join: %s\n", err) 182 | } 183 | 184 | fd2, err := utils.OpenInputFd(args[1]) 185 | if err != nil { 186 | utils.Die("join: %s\n", err) 187 | } 188 | 189 | outFd, err := utils.OpenOutputFd("-") 190 | if err != nil { 191 | utils.Die("join: %s\n", err) 192 | } 193 | 194 | cmdOpt.r1 = fd1 195 | cmdOpt.r2 = fd2 196 | cmdOpt.w = outFd 197 | 198 | cmdOpt.main() 199 | 200 | utils.CloseInputFd(fd1) 201 | utils.CloseInputFd(fd2) 202 | utils.CloseOutputFd(outFd) 203 | 204 | } 205 | -------------------------------------------------------------------------------- /join/join/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/guonaihong/coreutils/join" 5 | "os" 6 | ) 7 | 8 | func main() { 9 | join.Main(os.Args) 10 | } 11 | -------------------------------------------------------------------------------- /link/link.go: -------------------------------------------------------------------------------- 1 | package link 2 | 3 | import ( 4 | "fmt" 5 | "github.com/guonaihong/flag" 6 | "os" 7 | ) 8 | 9 | func Main(argv []string) { 10 | command := flag.NewFlagSet(argv[0], flag.ExitOnError) 11 | 12 | command.Parse(argv[1:]) 13 | 14 | args := command.Args() 15 | 16 | switch len(args) { 17 | case 0: 18 | fmt.Printf("link: missing operand\n") 19 | case 1: 20 | fmt.Printf("link: missing operand after '%s'\n", args[0]) 21 | case 2: 22 | if err := os.Link(args[0], args[1]); err != nil { 23 | fmt.Printf("%s\n", err) 24 | } 25 | default: 26 | fmt.Printf("link: extra operand '%s'\n", args[2]) 27 | 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /md5sum/README.md: -------------------------------------------------------------------------------- 1 | #### usage 2 | ```console 3 | Usage of coreutils: 4 | -b, --binary 5 | read in binary mode 6 | -c, --check string 7 | read MD5 sums from the FILEs and check them 8 | -ignore-missing 9 | don't fail or report status for missing files 10 | -quiet 11 | don't print OK for each successfully verified file 12 | -status 13 | don't output anything, status code shows success 14 | -strict 15 | exit non-zero for improperly formatted checksum lines 16 | -tag 17 | create a BSD-style checksum 18 | -w, --warn 19 | warn about improperly formatted checksum lines 20 | ``` 21 | -------------------------------------------------------------------------------- /md5sum/md5sum.go: -------------------------------------------------------------------------------- 1 | package md5sum 2 | 3 | import ( 4 | "github.com/guonaihong/coreutils/hashcore" 5 | ) 6 | 7 | func Main(argv []string) { 8 | hashcore.Main(argv, hashcore.Md5) 9 | } 10 | -------------------------------------------------------------------------------- /md5sum/md5sum/md5sum.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/guonaihong/coreutils/md5sum" 5 | "os" 6 | ) 7 | 8 | func main() { 9 | md5sum.Main(os.Args) 10 | } 11 | -------------------------------------------------------------------------------- /mv/README.md: -------------------------------------------------------------------------------- 1 | # mv 2 | 3 | #### 简介 4 | mv 5 | 6 | #### install 7 | ``` 8 | env GOPATH=`pwd` go get -u github.com/guonaihong/coreutils/mv 9 | ``` 10 | 11 | #### 12 | 13 | #### Examples 14 | 15 | ```bash 16 | mv myfile.txt myfiles 17 | ``` 18 | Move the file myfile.txt into the directory myfiles. If myfiles is a file, it will be overwritten. If the file is marked as read-only, but you own the file, you will be prompted before overwriting it. 19 | 20 | ```bash 21 | mv myfiles myfiles2 22 | ``` 23 | If myfiles is a file or directory, and myfiles2 is a directory, move myfiles into myfiles2. If myfiles2 does not exist, the file or directory myfiles is renamed myfiles2. 24 | 25 | ```bash 26 | mv myfile.txt ../ 27 | ``` 28 | Move the file myfile.txt into the parent directory of the current directory. 29 | 30 | ```bash 31 | mv -t myfiles myfile1 myfile2 32 | ``` 33 | Move the files myfile1 and myfile2 into the directory myfiles. 34 | 35 | ```bash 36 | mv myfile1 myfile2 myfiles 37 | ``` 38 | Same as the previous command. 39 | 40 | ```bash 41 | mv -n file file2 42 | ``` 43 | 44 | If file2 exists and is a directory, file is moved into it. If file2 does not exist, file is renamed file2. If file2 exists and is a file, nothing happens. 45 | ```bash 46 | mv -f file file2 47 | ``` 48 | If file2 exists and is a file, it will be overwritten. 49 | 50 | ```bash 51 | mv -i file file2 52 | ``` 53 | 54 | If file2 exists and is a file, a prompt is given: 55 | 56 | mv: overwrite 'file2'? 57 | Entering "y", "yes", "Yes", or "Y" will result in the file being overwritten. Any other input will skip the file. 58 | 59 | ```bash 60 | mv -fi file file2 61 | ``` 62 | Same as mv -i. Prompt before overwriting. The f option is ignored. 63 | 64 | ```bash 65 | mv -if file file2 66 | ``` 67 | 68 | Same as mv -f. Overwrite with no prompt. the i option is ignored. 69 | ```bash 70 | mv My\ file.txt My\ file\ 2.txt 71 | ``` 72 | 73 | Rename the file "My file.txt" to "My file 2.txt". Here, the spaces in the file name are escaped, protecting them from being interpreted as part of the command. 74 | 75 | ```bash 76 | mv "My file.txt" "My file 2.txt" 77 | 78 | ``` 79 | Same as the previous command. 80 | 81 | ```bash 82 | mv "My file.txt" myfiles 83 | ``` 84 | 85 | The result of this command: 86 | 87 | If myfiles a directory, My file.txt is moved into myfiles. 88 | If myfiles a file, My file.txt is renamed myfiles, and the original myfiles is overwritten. 89 | If myfiles does not exist, My file.txt is renamed myfiles. 90 | 91 | ```bash 92 | mv My*.txt myfiles 93 | ``` 94 | Here, * is a wildcard meaning "any number, including zero, of any character." 95 | 96 | If myfiles is a directory: all files with the extension .txt, whose name begins with My, will be moved into myfiles. 97 | If myfiles does not exist or is not a directory, mv reports an error and does nothing. 98 | 99 | ```bash 100 | my My\ file??.txt myfiles` 101 | ``` 102 | Here, ? is a wildcard that means "zero or one of any character." It's used twice, so it can match a maximum of two characters. 103 | 104 | If myfiles is a directory: any file with zero, one, or two characters between My file and .txt in their name is moved into myfiles. 105 | If myfiles doesn't exist, or is not a directory, mv reports an error and does nothing. 106 | 107 | ##### Making backups 108 | ```bash 109 | mv -b file file2 110 | ``` 111 | 112 | If file2 exists, it will be renamed to file2~. 113 | 114 | ```bash 115 | mv -b --suffix=.bak file file2 116 | ``` 117 | If file2 exists, it will be renamed to file2.bak. 118 | 119 | ```bash 120 | mv --backup=numbered file file2 121 | ``` 122 | If file2 exists, it will be renamed file2.~1~. If file2.~1~ exists, it will be renamed file2.~2~, etc. 123 | 124 | ```bash 125 | VERSION_CONTROL=numbered mv -b file file2 126 | ``` 127 | Same as previous command. The environment variable is defined for this command only. 128 | 129 | ```bash 130 | export VERSION_CONTROL=numbered; mv -b file file2 131 | ``` 132 | By exporting the VERSION_CONTROL environment variable, all mv -b commands for the current session will use numbered backups. 133 | 134 | ```bash 135 | export VERSION_CONTROL=numbered; mv file file2 136 | ``` 137 | Even though the VERSION_CONTROL variable is set, no backups are created because -b (or --backup) was not specified. If file2 exists, it is overwritten. 138 | 139 | * Example comes from(https://www.computerhope.com/unix/umv.htm) 140 | -------------------------------------------------------------------------------- /mv/mv.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "github.com/guonaihong/flag" 6 | "os" 7 | "strings" 8 | ) 9 | 10 | func exist(fileName string) bool { 11 | _, err := os.Stat(fileName) 12 | return !os.IsNotExist(err) 13 | } 14 | 15 | func deleteTrailingSlashes(s string) string { 16 | return strings.TrimRight(s, "/\\") 17 | } 18 | 19 | func readYes() bool { 20 | b := make([]byte, 512) 21 | n, err := os.Stdin.Read(b) 22 | if err != nil { 23 | return false 24 | } 25 | 26 | if string(b[:n]) != "y\n" { 27 | return false 28 | } 29 | return true 30 | } 31 | 32 | func main() { 33 | 34 | interactive := flag.Bool("i, interactive", false, "prompt before overwrite") 35 | force := flag.Bool("f, force", false, "do not prompt before overwriting") 36 | //backupSuffix := flag.String("backup", "", "make a backup of each existing destination file") 37 | backup := flag.Bool("b", false, "like --backup but does not accept an argument") 38 | noClobber := flag.Bool("n, no-clobber", false, "do not overwrite an existing file") 39 | stripTrailingSlashes := flag.Bool("strip-trailing-slashes", false, "remove any trailing slashes from each SOURCE argument") 40 | suffix := flag.String("S, suffix", "~", "override the usual backup suffix") 41 | targetDirectory := flag.String("t, target-directory", "", "move all SOURCE arguments into DIRECTORY") 42 | noTargetDirectory := flag.Bool("T, no-target-directory", false, "treat DEST as a normal file") 43 | //update := flag.Bool("u, update", false, "move only when the SOURCE file is newer than the destination file or when the destination file is missing") 44 | //verbose := flag.Bool("v, verbose", false, "explain what is being done") 45 | //context := flag.Bool("Z, context", false, "set SELinux security context of destination file to default type") 46 | //version := flag.Bool("version", false, "output version information and exit") 47 | 48 | flag.Parse() 49 | 50 | args := flag.Args() 51 | 52 | if len(args) <= 1 { 53 | flag.Usage() 54 | os.Exit(64) 55 | } 56 | 57 | source := args[:len(args)-1] 58 | target := args[len(args)-1] 59 | 60 | if len(*targetDirectory) > 0 { 61 | source = args 62 | target = *targetDirectory 63 | } 64 | 65 | if *noTargetDirectory { 66 | fmt.Println("icannot combine --target-directory (-t)", 67 | "and --no-target-directory (-T)") 68 | os.Exit(0) 69 | } 70 | 71 | for _, s := range source { 72 | 73 | if *stripTrailingSlashes { 74 | s = deleteTrailingSlashes(s) 75 | } 76 | // if target exists and is a directory, s is moved into it. 77 | newTarget := target + "/" + s 78 | if fi, err := os.Lstat(target); !os.IsNotExist(err) { 79 | mode := fi.Mode() 80 | if mode.IsRegular() { 81 | 82 | if *noClobber { 83 | continue 84 | } 85 | 86 | newTarget = target 87 | } 88 | } else { 89 | // if target does not exist, s is renamed target 90 | newTarget = target 91 | 92 | mode := fi.Mode() 93 | perm := mode.Perm() 94 | if !*force && int(perm)&os.O_WRONLY == 0 { 95 | fmt.Printf("mv: replace '%s', overriding mode %x (%v)?", newTarget, perm, perm) 96 | readYes() 97 | } 98 | } 99 | 100 | if *force { 101 | goto rename 102 | } 103 | 104 | if *interactive { 105 | if *force { 106 | goto rename 107 | } 108 | 109 | if exist(newTarget) { 110 | fmt.Printf("overwrite %s ? (y/n [n])", newTarget) 111 | if !readYes() { 112 | return 113 | } 114 | } 115 | 116 | } 117 | 118 | if *backup { 119 | if exist(newTarget) { 120 | err := os.Rename(newTarget, newTarget+*suffix) 121 | if err != nil { 122 | fmt.Printf("%s\n", err) 123 | } 124 | } 125 | 126 | } 127 | 128 | rename: 129 | err := os.Rename(s, target) 130 | if err != nil { 131 | fmt.Printf("%s\n", err) 132 | } 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /paste/README.md: -------------------------------------------------------------------------------- 1 | # paste 2 | 3 | #### 简介 4 | paste 在部分完成gnu paste 功能命令基础上,主要解决gnu paste一个很诡异的问题 5 | 6 | #### install 7 | ``` 8 | env GOPATH=`pwd` go get -u github.com/guonaihong/coreutils/paste/paste 9 | ``` 10 | 11 | #### 命令行选项 12 | ```console 13 | Usage of ./paste: 14 | -d, --delimiters string 15 | reuse characters from LIST instead of TABs 16 | -z, --zero-terminated 17 | line delimiter is NUL, not newline 18 | ``` 19 | 20 | #### gnu paste 诡异问题 21 | * gnu paste 22 | ``` 23 | paste -d "/" session_id.log session_id.log 24 | # 输出 25 | /c64e787e-30ae-45b5-b8ee-7037f8b92515 26 | /c64e787e-30ae-45b5-b8ee-7037f8b92515 27 | /c64e787e-30ae-45b5-b8ee-7037f8b92515 28 | ``` 29 | 30 | * paste 31 | ``` 32 | paste -d "/" session_id.log session_id.log 33 | 34 | # 输出 35 | c64e787e-30ae-45b5-b8ee-7037f8b92515/c64e787e-30ae-45b5-b8ee-7037f8b92515 36 | c64e787e-30ae-45b5-b8ee-7037f8b92515/c64e787e-30ae-45b5-b8ee-7037f8b92515 37 | c64e787e-30ae-45b5-b8ee-7037f8b92515/c64e787e-30ae-45b5-b8ee-7037f8b92515 38 | ``` 39 | -------------------------------------------------------------------------------- /paste/paste.go: -------------------------------------------------------------------------------- 1 | package paste 2 | 3 | import ( 4 | "bufio" 5 | "bytes" 6 | "github.com/guonaihong/flag" 7 | "os" 8 | "sync" 9 | ) 10 | 11 | func Main(argv []string) { 12 | 13 | var lineDelim = '\n' 14 | 15 | command := flag.NewFlagSet(argv[0], flag.ExitOnError) 16 | 17 | delim := command.Opt("d, delimiters", "reuse characters from LIST instead of TABs"). 18 | NewString("\t") 19 | 20 | zeroTerminated := command.Opt("z, zero-terminated", "line delimiter is NUL, not newline"). 21 | Flags(flag.PosixShort).NewBool(false) 22 | 23 | command.Parse(argv[1:]) 24 | 25 | if *zeroTerminated { 26 | lineDelim = '\000' 27 | } 28 | 29 | args := command.Args() 30 | 31 | wg := sync.WaitGroup{} 32 | 33 | wg.Add(len(args)) 34 | 35 | resultChan := make([]chan string, len(args)) 36 | 37 | for k, _ := range resultChan { 38 | resultChan[k] = make(chan string, 1) 39 | } 40 | 41 | defer wg.Wait() 42 | 43 | for id, fileName := range args { 44 | go func(id int, fileName string) { 45 | defer wg.Done() 46 | defer close(resultChan[id]) 47 | 48 | file, err := os.Open(fileName) 49 | if err != nil { 50 | os.Exit(1) 51 | } 52 | 53 | defer file.Close() 54 | 55 | br := bufio.NewReader(file) 56 | 57 | for { 58 | 59 | l, err := br.ReadBytes(byte(lineDelim)) 60 | if err != nil && len(l) == 0 { 61 | break 62 | } 63 | 64 | resultChan[id] <- string(l) 65 | } 66 | 67 | }(id, fileName) 68 | 69 | } 70 | 71 | out := &bytes.Buffer{} 72 | 73 | for { 74 | allQuit := true 75 | for k, v := range resultChan { 76 | line, ok := <-v 77 | 78 | if ok { 79 | allQuit = false 80 | } 81 | if k != 0 { 82 | out.WriteString(*delim) 83 | } 84 | out.WriteString(line) 85 | } 86 | 87 | if allQuit { 88 | break 89 | } 90 | 91 | out.WriteString("\n") 92 | os.Stdout.Write(out.Bytes()) 93 | out.Reset() 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /paste/paste/paste.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/guonaihong/coreutils/paste" 5 | "os" 6 | ) 7 | 8 | func main() { 9 | paste.Main(os.Args) 10 | } 11 | -------------------------------------------------------------------------------- /paste/session_id.log: -------------------------------------------------------------------------------- 1 | c64e787e-30ae-45b5-b8ee-7037f8b92515 2 | c64e787e-30ae-45b5-b8ee-7037f8b92515 3 | c64e787e-30ae-45b5-b8ee-7037f8b92515 4 | -------------------------------------------------------------------------------- /pwd/README.md: -------------------------------------------------------------------------------- 1 | # pwd 2 | 3 | #### summary 4 | pwd has the same pwd command as ubuntu 18.04 5 | 6 | #### install 7 | ``` 8 | env GOPATH=`pwd` go get -u github.com/guonaihong/coreutils/pwd/pwd 9 | ``` 10 | 11 | #### usage 12 | ```console 13 | Usage of pwd: 14 | -L, --logical 15 | print the value of $PWD if it names the current working 16 | directory 17 | -P, --physical 18 | print the physical directory, without any symbolic links 19 | ``` 20 | -------------------------------------------------------------------------------- /pwd/pwd.go: -------------------------------------------------------------------------------- 1 | package pwd 2 | 3 | import ( 4 | "fmt" 5 | "github.com/guonaihong/flag" 6 | "os" 7 | "path/filepath" 8 | ) 9 | 10 | type Pwd struct { 11 | Logical bool 12 | Physical bool 13 | } 14 | 15 | func New(argv []string) (*Pwd, []string) { 16 | p := Pwd{} 17 | 18 | command := flag.NewFlagSet(argv[0], flag.ExitOnError) 19 | 20 | command.Opt("L, logical", "print the value of $PWD if it names the current working\n"+ 21 | "directory").Flags(flag.PosixShort).DefaultVar(&p.Logical, true) 22 | 23 | command.Opt("P, physical", "print the physical directory, without any symbolic links"). 24 | Flags(flag.PosixShort).Var(&p.Physical) 25 | 26 | command.Parse(argv[1:]) 27 | return &p, command.Args() 28 | } 29 | 30 | func Main(argv []string) { 31 | p, _ := New(argv) 32 | 33 | dir, err := os.Getwd() 34 | if err != nil { 35 | fmt.Printf("pwd: %s\n", err) 36 | return 37 | } 38 | 39 | if p.Physical { 40 | dir, err = filepath.EvalSymlinks(dir) 41 | if err != nil { 42 | fmt.Printf("pwd: %s\n", dir) 43 | } 44 | } 45 | 46 | if p.Logical { 47 | fmt.Printf("%s\n", dir) 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /pwd/pwd/pwd.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/guonaihong/coreutils/pwd" 5 | "os" 6 | ) 7 | 8 | func main() { 9 | pwd.Main(os.Args) 10 | } 11 | -------------------------------------------------------------------------------- /realpath/realpath.go: -------------------------------------------------------------------------------- 1 | package realpath 2 | 3 | import ( 4 | "fmt" 5 | "github.com/guonaihong/flag" 6 | ) 7 | 8 | type RealPath struct { 9 | CanonicalizeExisting bool 10 | CanonicalizeMissing bool 11 | Logical bool 12 | Physical bool 13 | Quiet bool 14 | RelativeTo string 15 | RelativeBase bool 16 | NoSymlinks bool 17 | Zero bool 18 | } 19 | 20 | func New(argv []string) (*RealPath, []string) { 21 | 22 | realPath := &RealPath{} 23 | 24 | command := flag.NewFlagSet(argv[0]) 25 | 26 | command.Opt("e, canonicalize-existing", "all components of the path must exist"). 27 | Flags(flag.PosixShort).Var(&realPath.CanonicalizeExisting) 28 | 29 | command.Opt("m, canonicalize-missing", "no path components need exist or be a directory"). 30 | Flags(flag.PosixShort).Var(&realPath.CanonicalizeMissing) 31 | 32 | command.Opt("L, logical", "resolve '..' components before symlinks"). 33 | Flags(flag.PosixShort).Var(&realPath.Logical) 34 | 35 | command.Opt("P, physical", "resolve symlinks as encountered (default)"). 36 | Flags(flag.PosixShort).Var(&realPath.Physical) 37 | 38 | command.Opt("q, quiet", "suppress most error messages"). 39 | Flags(flag.PosixShort).Var(&realPath.Quiet) 40 | 41 | command.Opt("relative-to", "print the resolved path relative to DIR"). 42 | Flags(flag.PosixShort).Var(&realPath.RelativeTo) 43 | 44 | command.Opt("relative-base", "print absolute paths unless paths below DIR"). 45 | Flags(flag.PosixShort).Var(&realPath.RelativeBase) 46 | 47 | command.Opt("s, strip, no-symlinks", "don't expand symlinks"). 48 | Flags(flag.PosixShort).Var(&realPath.NoSymlinks) 49 | 50 | command.Opt("z, zero", "end each output line with NUL, not newline"). 51 | Flags(flag.PosixShort).Var(&realPath.Zero) 52 | 53 | command.Parse(argv[1:]) 54 | 55 | return realPath, command.Args() 56 | } 57 | -------------------------------------------------------------------------------- /rmdir/README.md: -------------------------------------------------------------------------------- 1 | # rmdir 2 | 3 | #### summary 4 | Rmidr has the same rmdir command as ubuntu 18.04 5 | 6 | #### install 7 | ``` 8 | env GOPATH=`pwd` go get -u github.com/guonaihong/coreutils/rmdir/rmdir 9 | ``` 10 | 11 | #### usage 12 | ```console 13 | Usage of rmdir: 14 | -ignore-fail-on-non-empty 15 | ignore each failure that is solely because a directory 16 | is non-empty 17 | -p, --parent 18 | remove DIRECTORY and its ancestors; e.g., 'rmdir -p a/b/c' is 19 | similar to 'rmdir a/b/c a/b a' 20 | ``` 21 | -------------------------------------------------------------------------------- /rmdir/rmdir.go: -------------------------------------------------------------------------------- 1 | package rmdir 2 | 3 | import ( 4 | "fmt" 5 | "github.com/guonaihong/flag" 6 | "os" 7 | ) 8 | 9 | type Rmdir struct { 10 | Parent *bool 11 | IgnoreFailOnNonEmpty *bool 12 | Verbose *bool 13 | } 14 | 15 | func New(argv []string) (*Rmdir, []string) { 16 | 17 | rmdir := &Rmdir{} 18 | 19 | command := flag.NewFlagSet(argv[0], flag.ExitOnError) 20 | 21 | rmdir.IgnoreFailOnNonEmpty = command.Opt("ignore-fail-on-non-empty", 22 | "ignore each failure that is solely because a directory\n"+ 23 | "is non-empty"). 24 | NewBool(false) 25 | 26 | rmdir.Verbose = command.Opt("v, verbose", 27 | "output a diagnostic for every directory processed"). 28 | Flags(flag.PosixShort).NewBool(false) 29 | 30 | rmdir.Parent = command.Opt("p, parent", 31 | "remove DIRECTORY and its ancestors; e.g., 'rmdir -p a/b/c' is\n"+ 32 | "similar to 'rmdir a/b/c a/b a'"). 33 | Flags(flag.PosixShort).NewBool(false) 34 | 35 | command.Parse(argv[1:]) 36 | 37 | return rmdir, command.Args() 38 | } 39 | 40 | func (r *Rmdir) Rmdir(name string) error { 41 | 42 | if r.Parent != nil && *r.Parent { 43 | return os.RemoveAll(name) 44 | } 45 | 46 | return os.Remove(name) 47 | } 48 | 49 | func (r *Rmdir) isDir(name string) (bool, error) { 50 | fi, err := os.Stat(name) 51 | 52 | if err != nil { 53 | return false, err 54 | } 55 | 56 | return fi.IsDir(), nil 57 | } 58 | 59 | func (r *Rmdir) IsIgnoreFailOnNonEmpty() bool { 60 | return r.IgnoreFailOnNonEmpty != nil && 61 | *r.IgnoreFailOnNonEmpty 62 | } 63 | 64 | func (r *Rmdir) IsVerbose() bool { 65 | return r.Verbose != nil && *r.Verbose 66 | } 67 | 68 | func exitCode(failCount *int) { 69 | if *failCount > 0 { 70 | os.Exit(1) 71 | } 72 | } 73 | 74 | func Main(argv []string) { 75 | 76 | rmdir, args := New(argv) 77 | 78 | failCount := 0 79 | defer exitCode(&failCount) 80 | 81 | for _, v := range args { 82 | 83 | if rmdir.IsVerbose() { 84 | fmt.Printf("rmdir: removing directory, '%s'\n", v) 85 | } 86 | 87 | isDir, err := rmdir.isDir(v) 88 | if err != nil { 89 | fmt.Printf("rmdir: %s\n", err) 90 | failCount++ 91 | continue 92 | } 93 | 94 | if isDir == false { 95 | fmt.Printf("rmdir: failed to remove '%s': Not a directory\n", v) 96 | failCount++ 97 | continue 98 | } 99 | 100 | err = rmdir.Rmdir(v) 101 | 102 | if err != nil { 103 | if rmdir.IsIgnoreFailOnNonEmpty() { 104 | continue 105 | } 106 | 107 | fmt.Printf("rmdir: %s\n", err) 108 | failCount++ 109 | } 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /rmdir/rmdir/rmdir.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/guonaihong/coreutils/rmdir" 5 | "os" 6 | ) 7 | 8 | func main() { 9 | rmdir.Main(os.Args) 10 | } 11 | -------------------------------------------------------------------------------- /runtest/runtest.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os/exec" 6 | ) 7 | 8 | func main() { 9 | command := []string{ 10 | "base32", 11 | "base64", 12 | "cat", 13 | "echo", 14 | "tr", 15 | "cut", 16 | "tail", 17 | "head", 18 | } 19 | 20 | for _, c := range command { 21 | runCmd := fmt.Sprintf("env GOPATH=`pwd` go test github.com/guonaihong/coreutils/%s", c) 22 | 23 | cmd := exec.Command("bash", "-c", runCmd) 24 | 25 | out, err := cmd.Output() 26 | if err != nil { 27 | fmt.Println(err) 28 | } 29 | 30 | fmt.Println(string(out)) 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /seq/README.md: -------------------------------------------------------------------------------- 1 | # seq 2 | 3 | #### summary 4 | seq has the same seq command as ubuntu 18.04 5 | 6 | #### install 7 | ``` 8 | env GOPATH=`pwd` go get -u github.com/guonaihong/coreutils/seq/seq 9 | ``` 10 | 11 | #### usage 12 | ```console 13 | Usage of seq: 14 | -f, --format string 15 | use printf style floating-point FORMAT 16 | -s, --separator string 17 | use STRING to separate numbers (default: \n) 18 | -w, --equal-width string 19 | equalize width by padding with leading zeroes 20 | ``` 21 | 22 | #### todo feature(%a or %A) 23 | ``` 24 | seq -f "%a" 100 25 | seq -f "%A" 100 26 | ``` 27 | -------------------------------------------------------------------------------- /seq/seq.go: -------------------------------------------------------------------------------- 1 | package seq 2 | 3 | import ( 4 | "fmt" 5 | "github.com/guonaihong/coreutils/utils" 6 | "github.com/guonaihong/flag" 7 | "io" 8 | "os" 9 | "strconv" 10 | "strings" 11 | ) 12 | 13 | type Seq struct { 14 | Format *string 15 | Separator *string 16 | EqualWidth *string 17 | 18 | first string 19 | step string 20 | last string 21 | } 22 | 23 | func New(argv []string) (*Seq, []string) { 24 | 25 | seq := Seq{} 26 | 27 | command := flag.NewFlagSet(argv[0], flag.ExitOnError) 28 | 29 | seq.Format = command.Opt("f, format", "use printf style floating-point FORMAT"). 30 | Flags(flag.PosixShort).NewString("") 31 | seq.Separator = command.Opt("s, separator", 32 | "use STRING to separate numbers (default: \\n)"). 33 | Flags(flag.PosixShort).NewString("\n") 34 | seq.EqualWidth = command.Opt("w, equal-width", 35 | "equalize width by padding with leading zeroes"). 36 | Flags(flag.PosixShort).NewString("") 37 | 38 | command.Parse(argv[1:]) 39 | 40 | args := command.Args() 41 | if len(args) == 0 { 42 | utils.Die("seq: missing operand\n") 43 | } 44 | 45 | seq.first = "1" 46 | seq.step = "1" 47 | if len(args) >= 3 { 48 | seq.first = args[0] 49 | seq.step = args[1] 50 | seq.last = args[2] 51 | } else if len(args) >= 2 { 52 | seq.first = args[0] 53 | seq.last = args[1] 54 | } else if len(args) >= 1 { 55 | seq.last = args[0] 56 | } 57 | 58 | return &seq, args 59 | } 60 | 61 | func (s *Seq) check(format string) error { 62 | count := strings.Count(format, "%") 63 | if count == 0 { 64 | return fmt.Errorf(`seq: format '%s' has no %% directive`, format) 65 | } 66 | 67 | if count > 1 { 68 | return fmt.Errorf(`seq: format '%s' has too many %% directive`, format) 69 | } 70 | 71 | pos := strings.Index(format, "%") 72 | switch format[pos+1 : pos+2] { 73 | case "e", "f", "E", "F": 74 | //case "a", "A": //todo 75 | case "G", "g": 76 | default: 77 | return fmt.Errorf("format %s has unknown %%%c directive", 78 | format, format[pos+1]) 79 | } 80 | return nil 81 | } 82 | 83 | func (s *Seq) Seq(w io.Writer) error { 84 | 85 | first, err := strconv.ParseFloat(s.first, 0) 86 | if err != nil { 87 | return fmt.Errorf("seq: invalid floating point argument: %s", s.first) 88 | } 89 | 90 | step, err := strconv.ParseFloat(s.step, 0) 91 | if err != nil { 92 | return fmt.Errorf("seq: invalid floating point argument: %s", s.step) 93 | } 94 | 95 | last, err := strconv.ParseFloat(s.last, 0) 96 | if err != nil { 97 | return fmt.Errorf("seq: invalid floating point argument: %s", s.last) 98 | } 99 | 100 | format := "%g" 101 | if s.Format != nil && len(*s.Format) > 0 { 102 | format = *s.Format 103 | } 104 | 105 | if err = s.check(format); err != nil { 106 | return err 107 | } 108 | 109 | var v interface{} 110 | separator := "\n" 111 | if s.Separator != nil { 112 | separator = *s.Separator 113 | } 114 | 115 | for ; first <= last; first += step { 116 | v = first 117 | fmt.Fprintf(w, format+separator, v) 118 | } 119 | 120 | return nil 121 | } 122 | 123 | func Main(argv []string) { 124 | s, _ := New(argv) 125 | 126 | err := s.Seq(os.Stdout) 127 | if err != nil { 128 | utils.Die("%s\n", err) 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /seq/seq/seq.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/guonaihong/coreutils/seq" 5 | "os" 6 | ) 7 | 8 | func main() { 9 | seq.Main(os.Args) 10 | } 11 | -------------------------------------------------------------------------------- /seq/seq_test.go: -------------------------------------------------------------------------------- 1 | package seq 2 | 3 | import ( 4 | "bytes" 5 | "testing" 6 | ) 7 | 8 | func testFirstEndStep(dst string, first, step, last string, t *testing.T) { 9 | s := Seq{} 10 | s.first = "1" 11 | s.step = "1" 12 | s.last = "10" 13 | 14 | w := &bytes.Buffer{} 15 | 16 | s.Seq(w) 17 | 18 | if dst != w.String() { 19 | t.Errorf("seq fail (%s) need(%s)\n", w.String(), dst) 20 | } 21 | } 22 | 23 | func TestFirstEndStep(t *testing.T) { 24 | dst := `1 25 | 2 26 | 3 27 | 4 28 | 5 29 | 6 30 | 7 31 | 8 32 | 9 33 | 10 34 | ` 35 | testFirstEndStep(dst, "1", "1", "10", t) 36 | } 37 | -------------------------------------------------------------------------------- /sha1sum/README.md: -------------------------------------------------------------------------------- 1 | #### usage 2 | ```console 3 | Usage of sha1sum: 4 | -b, --binary 5 | read in binary mode 6 | -c, --check string 7 | read SHA1 sums from the FILEs and check them 8 | -ignore-missing 9 | don't fail or report status for missing files 10 | -quiet 11 | don't print OK for each successfully verified file 12 | -status 13 | don't output anything, status code shows success 14 | -strict 15 | exit non-zero for improperly formatted checksum lines 16 | -tag 17 | create a BSD-style checksum 18 | -w, --warn 19 | warn about improperly formatted checksum lines 20 | ``` 21 | -------------------------------------------------------------------------------- /sha1sum/sha1sum.go: -------------------------------------------------------------------------------- 1 | package sha1sum 2 | 3 | import ( 4 | "github.com/guonaihong/coreutils/hashcore" 5 | ) 6 | 7 | func Main(argv []string) { 8 | hashcore.Main(argv, hashcore.Sha1) 9 | } 10 | -------------------------------------------------------------------------------- /sha1sum/sha1sum/sha1sum.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/guonaihong/coreutils/sha1sum" 5 | "os" 6 | ) 7 | 8 | func main() { 9 | sha1sum.Main(os.Args) 10 | } 11 | -------------------------------------------------------------------------------- /sha224sum/README.md: -------------------------------------------------------------------------------- 1 | #### usage 2 | ``` console 3 | Usage of sha224sum: 4 | -b, --binary 5 | read in binary mode 6 | -c, --check string 7 | read SHA224 sums from the FILEs and check them 8 | -ignore-missing 9 | don't fail or report status for missing files 10 | -quiet 11 | don't print OK for each successfully verified file 12 | -status 13 | don't output anything, status code shows success 14 | -strict 15 | exit non-zero for improperly formatted checksum lines 16 | -tag 17 | create a BSD-style checksum 18 | -w, --warn 19 | warn about improperly formatted checksum lines 20 | ``` 21 | -------------------------------------------------------------------------------- /sha224sum/sha224sum.go: -------------------------------------------------------------------------------- 1 | package sha224sum 2 | 3 | import ( 4 | "github.com/guonaihong/coreutils/hashcore" 5 | ) 6 | 7 | func Main(argv []string) { 8 | hashcore.Main(argv, hashcore.Sha224) 9 | } 10 | -------------------------------------------------------------------------------- /sha224sum/sha224sum/sha224sum.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/guonaihong/coreutils/sha224sum" 5 | "os" 6 | ) 7 | 8 | func main() { 9 | sha224sum.Main(os.Args) 10 | } 11 | -------------------------------------------------------------------------------- /sha256sum/README.md: -------------------------------------------------------------------------------- 1 | #### usage 2 | ``` console 3 | Usage of sha256sum: 4 | -b, --binary 5 | read in binary mode 6 | -c, --check string 7 | read SHA256 sums from the FILEs and check them 8 | -ignore-missing 9 | don't fail or report status for missing files 10 | -quiet 11 | don't print OK for each successfully verified file 12 | -status 13 | don't output anything, status code shows success 14 | -strict 15 | exit non-zero for improperly formatted checksum lines 16 | -tag 17 | create a BSD-style checksum 18 | -w, --warn 19 | warn about improperly formatted checksum lines 20 | ``` 21 | -------------------------------------------------------------------------------- /sha256sum/sha256sum.go: -------------------------------------------------------------------------------- 1 | package sha256sum 2 | 3 | import ( 4 | "github.com/guonaihong/coreutils/hashcore" 5 | ) 6 | 7 | func Main(argv []string) { 8 | hashcore.Main(argv, hashcore.Sha256) 9 | } 10 | -------------------------------------------------------------------------------- /sha256sum/sha256sum/sha256sum.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/guonaihong/coreutils/sha256sum" 5 | "os" 6 | ) 7 | 8 | func main() { 9 | sha256sum.Main(os.Args) 10 | } 11 | -------------------------------------------------------------------------------- /sha384sum/README.md: -------------------------------------------------------------------------------- 1 | #### usage 2 | ``` console 3 | Usage of sha384sum: 4 | -b, --binary 5 | read in binary mode 6 | -c, --check string 7 | read SHA384 sums from the FILEs and check them 8 | -ignore-missing 9 | don't fail or report status for missing files 10 | -quiet 11 | don't print OK for each successfully verified file 12 | -status 13 | don't output anything, status code shows success 14 | -strict 15 | exit non-zero for improperly formatted checksum lines 16 | -tag 17 | create a BSD-style checksum 18 | -w, --warn 19 | warn about improperly formatted checksum lines 20 | ``` 21 | -------------------------------------------------------------------------------- /sha384sum/sha384sum.go: -------------------------------------------------------------------------------- 1 | package sha384sum 2 | 3 | import ( 4 | "github.com/guonaihong/coreutils/hashcore" 5 | ) 6 | 7 | func Main(argv []string) { 8 | hashcore.Main(argv, hashcore.Sha384) 9 | } 10 | -------------------------------------------------------------------------------- /sha384sum/sha384sum/sha384sum.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/guonaihong/coreutils/sha384sum" 5 | "os" 6 | ) 7 | 8 | func main() { 9 | sha384sum.Main(os.Args) 10 | } 11 | -------------------------------------------------------------------------------- /sha512sum/README.md: -------------------------------------------------------------------------------- 1 | #### usage 2 | ```console 3 | Usage of sha512sum: 4 | -b, --binary 5 | read in binary mode 6 | -c, --check string 7 | read SHA512 sums from the FILEs and check them 8 | -ignore-missing 9 | don't fail or report status for missing files 10 | -quiet 11 | don't print OK for each successfully verified file 12 | -status 13 | don't output anything, status code shows success 14 | -strict 15 | exit non-zero for improperly formatted checksum lines 16 | -tag 17 | create a BSD-style checksum 18 | -w, --warn 19 | warn about improperly formatted checksum lines 20 | ``` 21 | -------------------------------------------------------------------------------- /sha512sum/sha512sum.go: -------------------------------------------------------------------------------- 1 | package sha512sum 2 | 3 | import ( 4 | "github.com/guonaihong/coreutils/hashcore" 5 | ) 6 | 7 | func Main(argv []string) { 8 | hashcore.Main(argv, hashcore.Sha512) 9 | } 10 | -------------------------------------------------------------------------------- /sha512sum/sha512sum/sha512sum.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/guonaihong/coreutils/sha512sum" 5 | "os" 6 | ) 7 | 8 | func main() { 9 | sha512sum.Main(os.Args) 10 | } 11 | -------------------------------------------------------------------------------- /shuf/README.md: -------------------------------------------------------------------------------- 1 | # shuf 2 | 3 | #### summary 4 | shuf has the same shuf command as ubuntu 18.04 5 | 6 | #### install 7 | ``` 8 | env GOPATH=`pwd` go get -u github.com/guonaihong/coreutils/shuf/shuf 9 | ``` 10 | 11 | #### usage 12 | ```console 13 | Usage of shuf: 14 | -e, --echo 15 | treat each ARG as an input line 16 | -i, --input-range string 17 | treat each number LO through HI as an input line 18 | -n, --head-count int 19 | output at most COUNT lines 20 | -o, --output string 21 | write result to FILE instead of standard output 22 | -r, --repeat 23 | output lines can be repeated 24 | -random-source string 25 | get random bytes from FILE 26 | -z, --zero-terminated 27 | line delimiter is NUL, not newline 28 | ``` 29 | -------------------------------------------------------------------------------- /shuf/shuf.go: -------------------------------------------------------------------------------- 1 | package shuf 2 | 3 | import ( 4 | "bufio" 5 | "fmt" 6 | "github.com/guonaihong/coreutils/utils" 7 | "github.com/guonaihong/flag" 8 | "io" 9 | "math/rand" 10 | "os" 11 | "strconv" 12 | "strings" 13 | ) 14 | 15 | type Shuf struct { 16 | Echo *bool 17 | InputRange *string 18 | HeadCount *int 19 | Output *string 20 | RandomSource *string 21 | Repeat *bool 22 | ZeroTerminated *bool 23 | lineDelim byte 24 | } 25 | 26 | func New(argv []string) (*Shuf, []string) { 27 | 28 | shuf := &Shuf{} 29 | 30 | command := flag.NewFlagSet(argv[0], flag.ExitOnError) 31 | 32 | shuf.Echo = command.Opt("e, echo", 33 | "treat each ARG as an input line"). 34 | Flags(flag.PosixShort).NewBool(false) 35 | 36 | shuf.InputRange = command.Opt("i, input-range", 37 | "treat each number LO through HI as an input line"). 38 | Flags(flag.PosixShort).NewString("") 39 | 40 | shuf.HeadCount = command.Opt("n, head-count", 41 | "output at most COUNT lines"). 42 | Flags(flag.PosixShort).NewInt(-1) 43 | 44 | shuf.Output = command.Opt("o, output", 45 | "write result to FILE instead of standard output"). 46 | Flags(flag.PosixShort).NewString("") 47 | 48 | shuf.RandomSource = command.Opt("random-source", 49 | "get random bytes from FILE"). 50 | Flags(flag.PosixShort).NewString("") 51 | 52 | shuf.Repeat = command.Opt("r, repeat", 53 | "output lines can be repeated"). 54 | Flags(flag.PosixShort).NewBool(false) 55 | 56 | shuf.ZeroTerminated = command.Opt("z, zero-terminated", 57 | "line delimiter is NUL, not newline"). 58 | Flags(flag.PosixShort).NewBool(false) 59 | 60 | command.Parse(argv[1:]) 61 | 62 | args := command.Args() 63 | 64 | return shuf, args 65 | } 66 | 67 | type result struct { 68 | m map[int]struct{} 69 | a [][]byte 70 | } 71 | 72 | func (r *result) init() { 73 | if r.m == nil { 74 | r.m = map[int]struct{}{} 75 | r.a = make([][]byte, 0, 10) 76 | } 77 | } 78 | 79 | func (r *result) add(no int, padding int, a []byte) { 80 | r.m[no-padding] = struct{}{} 81 | r.a = append(r.a, a) 82 | } 83 | 84 | func (r *result) del(no int) { 85 | delete(r.m, no) 86 | } 87 | 88 | func (s *Shuf) readFromFile(name string, r *result) error { 89 | 90 | s.lineDelim = byte('\n') 91 | 92 | if s.ZeroTerminated != nil && *s.ZeroTerminated { 93 | s.lineDelim = byte('\000') 94 | } 95 | 96 | fd, err := utils.OpenFile(name) 97 | if err != nil { 98 | return err 99 | } 100 | 101 | defer fd.Close() 102 | 103 | br := bufio.NewReader(fd) 104 | 105 | for no := 0; ; no++ { 106 | 107 | l, e := br.ReadBytes(s.lineDelim) 108 | if e != nil && len(l) == 0 { 109 | break 110 | } 111 | 112 | r.add(no, 0, l) 113 | } 114 | 115 | return nil 116 | } 117 | 118 | func (s *Shuf) Shuf(args []string, w io.Writer) error { 119 | 120 | r := &result{} 121 | 122 | r.init() 123 | 124 | if s.IsInputRange() { 125 | rs := strings.Split(*s.InputRange, "-") 126 | start, err := strconv.Atoi(rs[0]) 127 | if err != nil { 128 | return fmt.Errorf(`invalid input range: "%s"`, rs[0]) 129 | } 130 | 131 | end, err := strconv.Atoi(rs[1]) 132 | if err != nil { 133 | return fmt.Errorf(`invalid input range: "%s"`, rs[1]) 134 | } 135 | 136 | if start > end { 137 | return fmt.Errorf(`invalid input range: "%s"`, *s.InputRange) 138 | } 139 | 140 | i := start 141 | for ; start <= end; start++ { 142 | r.add(start, i, []byte(fmt.Sprintf("%d\n", start))) 143 | } 144 | 145 | } else if s.IsEcho() { 146 | 147 | for k, v := range args { 148 | r.add(k, 0, append([]byte(v), '\n')) 149 | } 150 | 151 | } else { 152 | s.readFromFile(args[0], r) 153 | } 154 | 155 | n := len(r.a) 156 | 157 | if s.HeadCount != nil && *s.HeadCount >= 0 { 158 | n = *s.HeadCount 159 | } 160 | 161 | var rnd *rand.Rand 162 | var err error 163 | 164 | if s.IsRandSource() { 165 | rnd, err = utils.GetRandSource(*s.RandomSource) 166 | if err != nil { 167 | return err 168 | } 169 | } 170 | 171 | for i := 0; ; i++ { 172 | 173 | if !s.IsRepeat() && i >= n { 174 | break 175 | } 176 | 177 | k := 0 178 | 179 | if rnd != nil { 180 | k = int(rnd.Int63n(int64(len(r.a)))) 181 | w.Write([]byte(r.a[k])) 182 | goto next 183 | } 184 | 185 | for k = range r.m { 186 | 187 | w.Write([]byte(r.a[k])) 188 | 189 | break 190 | } 191 | 192 | next: 193 | if !s.IsRepeat() { 194 | r.del(k) 195 | } 196 | } 197 | 198 | return nil 199 | } 200 | 201 | func (s *Shuf) IsRepeat() bool { 202 | return s.Repeat != nil && *s.Repeat 203 | } 204 | 205 | func (s *Shuf) IsRandSource() bool { 206 | return s.RandomSource != nil && len(*s.RandomSource) > 0 207 | } 208 | 209 | func (s *Shuf) IsEcho() bool { 210 | return s.Echo != nil && *s.Echo 211 | } 212 | 213 | func (s *Shuf) IsInputRange() bool { 214 | return s.InputRange != nil && len(*s.InputRange) > 0 215 | } 216 | 217 | func (s *Shuf) CheckInputRange(rangeStr string) error { 218 | 219 | if strings.Index(rangeStr, "-") == -1 { 220 | return fmt.Errorf(`invalid input range: "%s"`, rangeStr) 221 | } 222 | 223 | return nil 224 | } 225 | 226 | func Main(argv []string) { 227 | 228 | shuf, args := New(argv) 229 | 230 | if !*shuf.Echo && len(args) > 1 { 231 | utils.Die(fmt.Sprintf("shuf: extra operand '%s'\n", args[1])) 232 | } 233 | 234 | if len(*shuf.InputRange) > 0 && len(args) > 0 { 235 | utils.Die(fmt.Sprintf("shuf: extra operand '%s'\n", args[0])) 236 | } 237 | 238 | w := os.Stdout 239 | 240 | if len(*shuf.Output) > 0 { 241 | fd, err := os.Open(*shuf.Output) 242 | if err != nil { 243 | utils.Die("shuf: %s", *shuf.Output) 244 | } 245 | 246 | w = fd 247 | } 248 | 249 | if shuf.IsInputRange() { 250 | if err := shuf.CheckInputRange(*shuf.InputRange); err != nil { 251 | utils.Die("shuf: " + err.Error()) 252 | } 253 | } 254 | 255 | shuf.Shuf(args, w) 256 | utils.CloseOutputFd(w) 257 | } 258 | -------------------------------------------------------------------------------- /shuf/shuf/shuf.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/guonaihong/coreutils/shuf" 5 | "os" 6 | ) 7 | 8 | func main() { 9 | shuf.Main(os.Args) 10 | } 11 | -------------------------------------------------------------------------------- /sleep/README.md: -------------------------------------------------------------------------------- 1 | # sleep 2 | 3 | #### summary 4 | sleep 5 | 6 | #### install 7 | ``` 8 | env GOPATH=`pwd` go get -u github.com/guonaihong/coreutils/sleep/sleep 9 | ``` 10 | 11 | #### usage 12 | ```console 13 | Usage of sleep: 14 | -version 15 | output version information and exit 16 | ``` 17 | -------------------------------------------------------------------------------- /sleep/sleep.go: -------------------------------------------------------------------------------- 1 | package sleep 2 | 3 | import ( 4 | "fmt" 5 | "github.com/guonaihong/flag" 6 | "os" 7 | "time" 8 | ) 9 | 10 | func Main(argv []string) { 11 | command := flag.NewFlagSet(argv[0], flag.ExitOnError) 12 | version := command.Bool("version", false, "output version information and exit") 13 | command.Parse(argv[1:]) 14 | 15 | if *version { 16 | fmt.Printf("todo: output version\n") 17 | os.Exit(0) 18 | } 19 | 20 | args := command.Args() 21 | sleepCore := func(arg string) { 22 | 23 | i := 0 24 | rt := time.Second 25 | for ; i < len(arg); i++ { 26 | if arg[i] >= '0' && arg[i] <= '9' || arg[i] == '.' { 27 | continue 28 | } 29 | 30 | i++ 31 | break 32 | } 33 | 34 | i-- 35 | if len(arg)-i > 1 { 36 | fmt.Printf("Invalid interval:%s\n", arg) 37 | os.Exit(1) 38 | } 39 | 40 | t := 0.0 41 | fmt.Sscanf(arg, "%f", &t) 42 | 43 | if len(arg)-i == 1 { 44 | switch arg[len(arg)-1] { 45 | case 's', 'S': 46 | rt = time.Second 47 | case 'm', 'M': 48 | rt = time.Minute 49 | case 'h', 'H': 50 | rt = time.Hour 51 | case 'd', 'D': 52 | rt = time.Hour * 24 53 | } 54 | 55 | } 56 | 57 | rt = time.Duration(float64(rt) * t) 58 | time.Sleep(rt) 59 | } 60 | 61 | for _, v := range args { 62 | sleepCore(v) 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /sleep/sleep/sleep.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/guonaihong/coreutils/sleep" 5 | "os" 6 | ) 7 | 8 | func main() { 9 | sleep.Main(os.Args) 10 | } 11 | -------------------------------------------------------------------------------- /sort/README.md: -------------------------------------------------------------------------------- 1 | # sort 2 | 3 | #### 简介 4 | sort 5 | 6 | #### install 7 | ``` 8 | env GOPATH=`pwd` go get -u github.com/guonaihong/coreutils/sort 9 | ``` 10 | 11 | #### Example 12 | (下面的示例来自linux shell scripting cookbook) 13 | 14 | 对一组文件进行排序 15 | ``` 16 | sort file1.txt file2.txt file3.txt >sorted.txt 17 | sort file1.txt file2.txt file3.txt -o sorted.txt 18 | ``` 19 | 20 | 按照数字顺序进行排序 21 | ``` 22 | sort -n file.txt 23 | ``` 24 | 25 | 按照逆序进行排序 26 | ``` 27 | sort -r file.txt 28 | ``` 29 | 30 | 按照月份进行排序(依照一月,二月,三月) 31 | ``` 32 | sort -M months.txt 33 | ``` 34 | 35 | 合并两个已排序过的文件 36 | ``` 37 | sort -m sorted1 sorted2 38 | ``` 39 | 40 | 检查文件是否排过序 41 | ``` 42 | sort -C filename 43 | ``` 44 | 45 | ``` 46 | cat data.txt 47 | 1 mac 2000 48 | 2 winxp 4000 49 | 3 bsd 1000 50 | 4 linux 1000 51 | ``` 52 | 有很多方法可以对这段文本排序。目前它是按照序号(第一列)来排序的。我们也可以依据第二列和第三列来排序。 53 | -k指定了排序所依据的字符。如果是单个数字,则指的是列号。-r告诉sort命令按照逆序进行排序。例如 54 | ``` 55 | #依据第1列,以逆序形式排序 56 | sort -nrk 1 data.txt 57 | 4 linux 1000 58 | 3 bsd 1000 59 | 2 winxp 4000 60 | 1 mac 2000 61 | 62 | # -nr 表明按照数字顺序,采用逆序形式排序 63 | # 依据第2列进行排序 64 | 65 | sort -k 2 data.txt 66 | 3 bsd 1000 67 | 4 linux 1000 68 | 1 mac 2000 69 | 2 winxp 4000 70 | ``` 71 | 72 | -k后的整数指定了文本文件中的某一列。列与列之间有空格分隔。如果需要将特定范围内的一组字符(例如,第2列中的第4~5个字符)作为键,应该使用由 73 | 点号分隔的两个整数来定义一个字符位置,然后将该范围内的第一个字符和最后一个字符用逗号连接起来: 74 | ``` 75 | cat data.txt 76 | 1 alpha 300 77 | 2 beta 200 78 | 3 gamma 100 79 | 80 | sort -bk 2.3,2.4 data 81 | 82 | 3 gamma 100 83 | 1 alpha 300 84 | 2 beta 200 85 | ``` 86 | 87 | 把作为排序依据的字符写成数值键。为了提取出这些字符,用其在行内的起止位置作为键的书写格式(在上面的例子中,起止位置是2和3)。 88 | 用第一个字符作为键。 89 | ``` 90 | sort -nk 1,1 dat.txt 91 | ``` 92 | 为了使sort的输出与以\0作为终止符的xargs命令相兼容,采用下面的命令 93 | ``` 94 | sort -z data.txt|xargs -0 95 | #终止符\0用来确保安全地使用xargs命令 96 | ``` 97 | 有时文本中可能会包含一些像空格之类的多余字符。如果需要忽略标点符号并以字典序排序,可以使用: 98 | sort -bd unsorted.txt 99 | 其中,选项-b用于忽略文件中的前导空白行,选项-d用于指明以字典序进行排序。 100 | ``` 101 | 102 | ```` 103 | cat data.txt 104 | hello world 105 | abcde 106 | 12o2o2o2 107 | 44444 108 | 11111 109 | 33333 110 | 1 111 | 1G 112 | 2G 113 | 3G 114 | 115 | cat data.txt|sort -n 116 | ```` 117 | -------------------------------------------------------------------------------- /split/README.md: -------------------------------------------------------------------------------- 1 | # split 2 | 3 | #### 简介 4 | split 5 | 6 | #### install 7 | ``` 8 | env GOPATH=`pwd` go get -u github.com/guonaihong/coreutils/split 9 | ``` 10 | 11 | #### Example 12 | (下面的示例来自linux shell scripting cookbook) 和本人构造的测试用例 13 | 14 | 有时候必须把文件分割成多个更小的片段。比如把大的pcm音频文件切成一堆小的音频文件。 15 | split命令可以用来分割文件。该命令接受文件名作为参数,然后创建出一系列体积更小的文件, 16 | 其中依据字母序排在首位的那个文件对应于原始文件的第一部分,排在次位的文件对应于原始文件 17 | 的第二部分,以此类推。 18 | 例如,通过指定分割大小,可以将100KB的文件分成一系列10KB的小文件。在split命令中,除了k(KB) 19 | 我们还可以使用M(MB),G(GB), c(byte), w(word) 20 | 21 | ``` 22 | split -b 10k data.file 23 | ls 24 | data.file xaa xab xac xad xae xaf xag xah xai xaj 25 | ``` 26 | 27 | 上而后命令将data.file分割成了10个大小为10KB的文件。这些新文件以xab,xac,xad的形式命名。 28 | split默认使用字母后缀。如果想使用数字后缀,需要使用-d选项。此外,-a length可以指定后缀长度: 29 | ``` 30 | split -b 10k data.file -d -a 4 31 | ``` 32 | 33 | ``` 34 | ls 35 | data.file x0009 x0019 x0029 x0039 x0049 x0059 x0069 x0079 36 | ``` 37 | 38 | 为分割后的文件指定文件名前缀 39 | ``` 40 | split -b 10k data.file -d -a 4 split_file 41 | 42 | data.file split_file0002 split_file003 43 | ``` 44 | 45 | 如果不想按照数据块大小,而是根据行数来分割文件的话,可以使用-l no_of_lines: 46 | ``` 47 | split -l 10 data.file 48 | ``` 49 | -------------------------------------------------------------------------------- /split/split.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/guonaihong/flag" 5 | ) 6 | 7 | func main() { 8 | flag.Int("a, suffix-length", 0, "use suffixes of length N (default 2)") 9 | flag.String("additional-suffix", "", "append an additional SUFFIX to file names") 10 | flag.String("b, bytes", "", "put SIZE bytes per output file") 11 | flag.String("C, line-bytes", "", "put at most SIZE bytes of lines per output file") 12 | flag.String("d", "", "use numeric suffixes starting at 0, not alphabetic") 13 | flag.String("numeric-suffixes", "", "same as -d, but allow setting the start value") 14 | flag.String("x", "", "use hex suffixes starting at 0, not alphabetic") 15 | flag.String("hex-suffixes", "", "same as -x, but allow setting the start value") 16 | flag.String("e, elide-empty-files", "", "do not generate empty output files with '-n'") 17 | flag.String("filter", "", "write to shell COMMAND; file name is $FILE") 18 | flag.String("l, lines", "", "put NUMBER lines per output file") 19 | flag.String("n, number", "", "generate CHUNKS output files; see explanation below") 20 | flag.String("t, separator", "", "use SEP instead of newline as the record separator; '\000' (zero) specifies the NUL character") 21 | flag.Bool("u, unbuffered", false, "immediately copy input to output with '-n r/...'") 22 | flag.Bool("verbose", false, "print a diagnostic just before each output file is opened") 23 | 24 | flag.Parse() 25 | } 26 | -------------------------------------------------------------------------------- /tac/README.md: -------------------------------------------------------------------------------- 1 | # tac 2 | 3 | #### install 4 | ``` 5 | env GOPATH=`pwd` go get -u github.com/guonaihong/coreutils/tac/tac 6 | ``` 7 | 8 | #### usage 9 | ```console 10 | Usage of tac: 11 | -b, --before 12 | attach the separator before instead of after 13 | -r, --regex 14 | interpret the separator as a regular expression 15 | -s, --separator string 16 | use STRING as the separator instead of newline 17 | ``` 18 | -------------------------------------------------------------------------------- /tac/tac.go: -------------------------------------------------------------------------------- 1 | package tac 2 | 3 | import ( 4 | "bytes" 5 | "github.com/guonaihong/coreutils/utils" 6 | "github.com/guonaihong/flag" 7 | "io" 8 | "io/ioutil" 9 | "os" 10 | "regexp" 11 | ) 12 | 13 | const bufSize = 8092 14 | 15 | type Tac struct { 16 | Before *bool 17 | Regex *bool 18 | Separator *string 19 | pattern *regexp.Regexp 20 | BufSize int 21 | } 22 | 23 | func New(argv []string) (*Tac, []string) { 24 | t := Tac{} 25 | command := flag.NewFlagSet(argv[0], flag.ExitOnError) 26 | t.Before = command.Opt("b, before", "attach the separator before instead of after"). 27 | Flags(flag.PosixShort). 28 | NewBool(false) 29 | t.Regex = command.Opt("r, regex", "interpret the separator as a regular expression"). 30 | Flags(flag.PosixShort). 31 | NewBool(false) 32 | t.Separator = command.Opt("s, separator", "use STRING as the separator instead of newline"). 33 | Flags(flag.PosixShort). 34 | NewString("\n") 35 | 36 | command.Parse(argv[1:]) 37 | args := command.Args() 38 | 39 | args = utils.NewArgs(args) 40 | return &t, args 41 | } 42 | 43 | func printOffset(rs io.ReadSeeker, w io.Writer, buf []byte, start, end int64) error { 44 | 45 | curPos, err := rs.Seek(0, 1) 46 | if err != nil { 47 | return err 48 | } 49 | 50 | _, err = rs.Seek(start, 0) 51 | if err != nil { 52 | return err 53 | } 54 | 55 | defer rs.Seek(curPos, 0) 56 | 57 | for { 58 | 59 | if start >= end { 60 | break 61 | } 62 | 63 | needRead := end - start 64 | if int(needRead) > len(buf) { 65 | needRead = int64(len(buf)) 66 | } 67 | 68 | n, e := rs.Read(buf[:needRead]) 69 | if e != nil { 70 | break 71 | } 72 | 73 | w.Write(buf[:n]) 74 | start += int64(n) 75 | } 76 | return nil 77 | } 78 | 79 | func (t *Tac) readFromTailStdin(r io.Reader, w io.Writer, sep []byte, before bool) error { 80 | all, err := ioutil.ReadAll(r) 81 | if err != nil { 82 | return err 83 | } 84 | 85 | offset := make([]int, 0, 50) 86 | seps := make([]int, 0, 50) 87 | 88 | for i := 0; i < len(all); { 89 | pos := 0 90 | sepLen := len(sep) 91 | 92 | if t.pattern != nil { 93 | p := t.pattern.FindIndex(all[i:]) 94 | if p == nil { 95 | break 96 | } 97 | sepLen = p[1] - p[0] 98 | pos = p[0] 99 | seps = append(seps, sepLen) 100 | } else { 101 | 102 | pos = bytes.Index(all[i:], sep) 103 | if pos == -1 { 104 | break 105 | } 106 | } 107 | 108 | offset = append(offset, i+pos) 109 | i += pos + sepLen 110 | } 111 | 112 | if len(offset) == 0 { 113 | offset = append(offset, len(all)) 114 | sep = []byte("") 115 | } 116 | 117 | right := len(all) 118 | 119 | for i := len(offset) - 1; i >= 0; i-- { 120 | start := offset[i] 121 | sepLen := len(sep) 122 | if len(seps) > 0 { 123 | sepLen = seps[i] 124 | } 125 | 126 | if !before { 127 | start += sepLen 128 | } 129 | 130 | w.Write(all[start:right]) 131 | 132 | right = start 133 | } 134 | 135 | w.Write(all[0:right]) 136 | return nil 137 | } 138 | 139 | func (t *Tac) readFromTail(rs io.ReadSeeker, w io.Writer, sep []byte, before bool) error { 140 | 141 | if t.pattern != nil { 142 | return t.readFromTailStdin(rs, w, sep, before) 143 | } 144 | 145 | tail, err := rs.Seek(0, 2) 146 | if err != nil { 147 | return t.readFromTailStdin(rs, w, sep, before) 148 | } 149 | 150 | head := tail 151 | 152 | buf := make([]byte, t.BufSize+len(sep)) 153 | buf2 := make([]byte, t.BufSize) 154 | 155 | for head > 0 { 156 | 157 | minRead := head 158 | if minRead > int64(t.BufSize) { 159 | minRead = int64(t.BufSize) 160 | } 161 | 162 | _, err := rs.Seek(-minRead, 1) 163 | if err != nil { 164 | return err 165 | } 166 | 167 | n, err := rs.Read(buf[:minRead]) 168 | if err != nil { 169 | return err 170 | } 171 | 172 | head -= minRead 173 | rs.Seek(-minRead, 1) 174 | 175 | right := n 176 | h := n 177 | 178 | for { 179 | pos := bytes.LastIndex(buf[:h], sep) 180 | 181 | if pos == -1 { 182 | //not found 183 | break 184 | } 185 | 186 | if pos >= 0 { 187 | 188 | start := pos + len(sep) 189 | 190 | /* 191 | l := right - start 192 | fmt.Printf("%p, head = %d, pos = %d, start = %d, right = %d, right - start = %d, (%s)\n", 193 | t, head, pos, start, right, right-start, buf[start:]) 194 | */ 195 | 196 | if !bytes.Equal(buf[start:right], sep) { 197 | right = pos + len(sep) 198 | } 199 | 200 | h = pos 201 | 202 | /* 203 | fmt.Printf("1.l = %d, tail = %d, head = %d, start = %d pos = %d, buf(%s)\n", 204 | l, tail, head, int(head)+pos+len(sep), pos, buf[:n]) 205 | */ 206 | 207 | start = int(head) + pos 208 | 209 | if !before { 210 | start += len(sep) 211 | } 212 | 213 | if tail > int64(start) { 214 | err = printOffset(rs, w, buf2, int64(start), tail) 215 | if err != nil { 216 | return err 217 | } 218 | //Move tail position 219 | tail = int64(start) 220 | } 221 | //fmt.Printf("2.l = %d, tail = %d, head = %d, start = %d\n", l, tail, head, int(head)+pos+len(sep)) 222 | 223 | if pos == 0 { 224 | break 225 | } 226 | } 227 | 228 | } 229 | } 230 | 231 | if tail > 0 { 232 | printOffset(rs, w, buf2, 0, tail) 233 | } 234 | return nil 235 | } 236 | 237 | func (t *Tac) Tac(rs io.ReadSeeker, w io.Writer) error { 238 | before := false 239 | if t.Before != nil { 240 | before = *t.Before 241 | } 242 | 243 | if t.BufSize == 0 { 244 | t.BufSize = bufSize 245 | } 246 | 247 | if t.Separator != nil && 2*len(*t.Separator) > t.BufSize { 248 | t.BufSize = 2 * len(*t.Separator) 249 | } 250 | 251 | if t.Separator != nil { 252 | if t.Regex != nil && *t.Regex { 253 | t.pattern = regexp.MustCompile(*t.Separator) 254 | } 255 | 256 | err := t.readFromTail(rs, w, []byte(*t.Separator), before) 257 | if err != nil { 258 | return err 259 | } 260 | } 261 | return nil 262 | } 263 | 264 | func Main(argv []string) { 265 | 266 | tac, args := New(argv) 267 | 268 | for _, fileName := range args { 269 | f, err := utils.OpenFile(fileName) 270 | if err != nil { 271 | utils.Die("tac: %s\n", err) 272 | } 273 | 274 | err = tac.Tac(f, os.Stdout) 275 | if err != nil { 276 | utils.Die("tac: %s\n", err) 277 | } 278 | f.Close() 279 | } 280 | } 281 | -------------------------------------------------------------------------------- /tac/tac/tac.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/guonaihong/coreutils/tac" 5 | "os" 6 | ) 7 | 8 | func main() { 9 | tac.Main(os.Args) 10 | } 11 | -------------------------------------------------------------------------------- /tac/tac_test.go: -------------------------------------------------------------------------------- 1 | package tac 2 | 3 | import ( 4 | "bytes" 5 | "github.com/guonaihong/coreutils/utils" 6 | "strings" 7 | "testing" 8 | ) 9 | 10 | func testRegex(dst, src string, sep string, t *testing.T) { 11 | rs := strings.NewReader(src) 12 | w := &bytes.Buffer{} 13 | 14 | tac := Tac{} 15 | tac.Separator = utils.String(sep) 16 | tac.Regex = utils.Bool(true) 17 | tac.Tac(rs, w) 18 | 19 | if w.String() != dst { 20 | t.Fatalf("tac -r -s regexp-string fail(%s, %d), need(%s)\n", w.String(), w.Len(), dst) 21 | } 22 | } 23 | 24 | func TestRegex(t *testing.T) { 25 | src := `1 26 | 2 27 | 3 28 | 4 29 | 5 30 | 6 31 | 7 32 | 8 33 | 9 34 | 10 35 | ` 36 | dst := ` 37 | 38 | 10 39 | 9 40 | 8 41 | 7 42 | 6 43 | 5 44 | 4 45 | 3 46 | 21` 47 | testRegex(dst, src, `\d+`, t) 48 | } 49 | 50 | // tac -s string 51 | func testReadFromTailStdin(src, dst string, sep string, t *testing.T) { 52 | rs := strings.NewReader(src) 53 | w := &bytes.Buffer{} 54 | 55 | tac := Tac{} 56 | tac.readFromTailStdin(rs, w, []byte(sep), false) 57 | 58 | if w.String() != dst { 59 | t.Fatalf("tac -s fail(%s, %d), need(%s)\n", w.String(), w.Len(), dst) 60 | } 61 | } 62 | 63 | // tac -s string 64 | func testSeparator(src, dst string, sep string, bufSize int, t *testing.T) { 65 | tac := Tac{} 66 | tac.Separator = utils.String(sep) 67 | tac.Before = utils.Bool(false) 68 | tac.BufSize = bufSize 69 | rs := strings.NewReader(src) 70 | w := &bytes.Buffer{} 71 | 72 | tac.Tac(rs, w) 73 | 74 | if w.String() != dst { 75 | t.Fatalf("tac -s fail(%s), need(%s)\n", w.String(), dst) 76 | } 77 | } 78 | 79 | func TestSeparator(t *testing.T) { 80 | src := `1 81 | 2 82 | 3 83 | 4 84 | 5 85 | 6 86 | 7 87 | 8 88 | 9 89 | 10 90 | ` 91 | dst := `10 92 | 9 93 | 8 94 | 7 95 | 6 96 | 5 97 | 4 98 | 3 99 | 2 100 | 1 101 | ` 102 | testReadFromTailStdin(src, dst, "\n", t) 103 | testSeparator(src, dst, "\n", 0, t) 104 | testSeparator(src, dst, "\n", 3, t) 105 | //============================= 106 | 107 | src = "123aaa456aaa789aaa\n" 108 | dst = ` 109 | 789aaa456aaa123aaa` 110 | 111 | testReadFromTailStdin(src, dst, "aaa", t) 112 | testSeparator(src, dst, "aaa", 0, t) 113 | testSeparator(src, dst, "aaa", 6, t) 114 | //============================= 115 | 116 | src = "wwwwwwwwwwww" 117 | testReadFromTailStdin(src, src, "aaa", t) 118 | testSeparator(src, src, "aaa", 0, t) 119 | testSeparator(src, src, "aaa", 3, t) 120 | 121 | src = "1,2\n" 122 | testReadFromTailStdin(src, src, "\n", t) 123 | testSeparator(src, src, "\n", 0, t) 124 | testSeparator(src, src, "\n", 4, t) 125 | 126 | dst = `2 127 | 1,` 128 | testReadFromTailStdin(src, dst, ",", t) 129 | testSeparator(src, dst, ",", 0, t) 130 | testSeparator(src, dst, ",", 2, t) 131 | 132 | src = `wwwwww` 133 | testReadFromTailStdin(src, src, "www", t) 134 | testSeparator(src, src, "www", 0, t) 135 | testSeparator(src, src, "www", 3, t) 136 | } 137 | 138 | //tac -b 139 | func testBeforeReadFromTailStdin(src, dst, sep string, t *testing.T) { 140 | rs := strings.NewReader(src) 141 | w := &bytes.Buffer{} 142 | 143 | tac := Tac{} 144 | tac.readFromTailStdin(rs, w, []byte(sep), true) 145 | 146 | if w.String() != dst { 147 | t.Fatalf("tac -s fail(%s, l:%d), need(%s)\n", w.String(), w.Len(), dst) 148 | } 149 | } 150 | 151 | //tac -b 152 | func testSeparatorBefore(src, dst, sep string, bufSize int, t *testing.T) { 153 | tac := Tac{} 154 | tac.Separator = utils.String(sep) 155 | tac.Before = utils.Bool(true) 156 | tac.BufSize = bufSize 157 | 158 | rs := strings.NewReader(src) 159 | w := &bytes.Buffer{} 160 | 161 | tac.Tac(rs, w) 162 | 163 | if w.String() != dst { 164 | t.Fatalf("tac -s fail(%s, l:%d), need(%s)\n", w.String(), w.Len(), dst) 165 | } 166 | } 167 | 168 | func TestBefore(t *testing.T) { 169 | src := `1,2 170 | ` 171 | dst := `,2 172 | 1` 173 | 174 | testBeforeReadFromTailStdin(src, dst, ",", t) 175 | testSeparatorBefore(src, dst, ",", 3, t) 176 | 177 | src = `1 178 | 2 179 | 3 180 | 4 181 | 5 182 | 6 183 | 7 184 | 8 185 | 9 186 | 10 187 | ` 188 | dst = ` 189 | 190 | 10 191 | 9 192 | 8 193 | 7 194 | 6 195 | 5 196 | 4 197 | 3 198 | 21` 199 | testBeforeReadFromTailStdin(src, dst, "\n", t) 200 | testSeparatorBefore(src, dst, "\n", 3, t) 201 | 202 | src = `wwwwwwwwwwwwwww` 203 | testBeforeReadFromTailStdin(src, src, "www", t) 204 | testSeparatorBefore(src, src, "www", 3, t) 205 | } 206 | -------------------------------------------------------------------------------- /tail/README.md: -------------------------------------------------------------------------------- 1 | # tail 2 | 3 | #### summary 4 | tail 5 | 6 | #### install 7 | ``` 8 | env GOPATH=`pwd` go get -u github.com/guonaihong/coreutils/tail/tail 9 | ``` 10 | 11 | #### usage 12 | ``` console 13 | Usage of tail: 14 | -F same as --follow=name --retry 15 | -^\d+$, --n, --lines string 16 | output appended data as the file grows; 17 | an absent option argument means 'descriptor' 18 | -c, --bytes string 19 | output the last NUM bytes; or use -c +NUM to 20 | output starting with byte NUM of each file 21 | -f output appended data as the file grows; 22 | an absent option argument means 'descriptor' 23 | -f, --follow string 24 | output appended data as the file grows; 25 | an absent option argument means 'descriptor' 26 | -max-unchanged-stats string 27 | with --follow=name, reopen a FILE which has not 28 | changed size after N (default 5) iterations 29 | to see if it has been unlinked or renamed 30 | (this is the usual case of rotated log files); 31 | with inotify, this option is rarely useful 32 | -pid string 33 | with -f, terminate after process ID, PID dies 34 | -q, --quiet, --silent 35 | never print headers giving file names 36 | -retry 37 | keep trying to open a file if it is inaccessible 38 | -s, --sleep-interval float 39 | with -f, sleep for approximately N seconds 40 | (default 1.0) between iterations; 41 | with inotify and --pid=P, check process P at 42 | least once every N seconds 43 | -v, --verbose 44 | always print headers giving file names 45 | -z, --zero-terminated 46 | line delimiter is NUL, not newline 47 | 48 | ``` 49 | #### 如下一些频率不高的选项后面实现 50 | ```console 51 | --pid todo 52 | --retry todo 53 | --max-unchanged-status todo 54 | --follow todo 55 | ``` 56 | -------------------------------------------------------------------------------- /tail/tail.go: -------------------------------------------------------------------------------- 1 | package tail 2 | 3 | import ( 4 | "bufio" 5 | "fmt" 6 | "github.com/guonaihong/coreutils/utils" 7 | "github.com/guonaihong/flag" 8 | "io" 9 | "os" 10 | "time" 11 | ) 12 | 13 | type Tail struct { 14 | Bytes *string 15 | Follow *string 16 | Lines *string 17 | Quiet *bool 18 | Verbose *bool 19 | SleepInterval *float64 20 | Retry *bool 21 | MaxUnchangedStats *string 22 | LineDelim byte 23 | Pid *string 24 | } 25 | 26 | func New(argv []string) (*Tail, []string) { 27 | command := flag.NewFlagSet(argv[0], flag.ExitOnError) 28 | 29 | t := Tail{} 30 | t.Bytes = command.Opt("c, bytes", "output the last NUM bytes; or use -c +NUM to\n"+ 31 | "output starting with byte NUM of each file"). 32 | Flags(flag.PosixShort). 33 | NewString("0") 34 | 35 | follow := command.Opt("f", "output appended data as the file grows;\n"+ 36 | "an absent option argument means 'descriptor'"). 37 | Flags(flag.PosixShort). 38 | NewBool(false) 39 | 40 | t.Follow = command.Opt("f, follow", "output appended data as the file grows;\n"+ 41 | "an absent option argument means 'descriptor'"). 42 | Flags(flag.PosixShort). 43 | NewString("") 44 | 45 | _ = command.Opt("F", "same as --follow=name --retry"). 46 | Flags(flag.PosixShort). 47 | NewBool(false) 48 | 49 | t.Lines = command.OptOpt( 50 | flag.Flag{ 51 | Regex: `^\d+$`, 52 | Short: []string{"n"}, 53 | Long: []string{"lines"}, 54 | Usage: "output appended data as the file grows;\n" + 55 | "an absent option argument means 'descriptor'"}). 56 | Flags(flag.RegexKeyIsValue | flag.PosixShort). 57 | NewString("10") 58 | 59 | t.MaxUnchangedStats = command.Opt("max-unchanged-stats", "with --follow=name, reopen a FILE which has not\n"+ 60 | "changed size after N (default 5) iterations\n"+ 61 | "to see if it has been unlinked or renamed\n"+ 62 | "(this is the usual case of rotated log files);\n"+ 63 | "with inotify, this option is rarely useful"). 64 | NewString("") 65 | 66 | t.Pid = command.Opt("pid", "with -f, terminate after process ID, PID dies"). 67 | Flags(flag.PosixShort). 68 | NewString("") 69 | 70 | t.Quiet = command.Opt("q, quiet, silent", "never print headers giving file names"). 71 | Flags(flag.PosixShort). 72 | NewBool(false) 73 | 74 | t.Retry = command.Opt("retry", "keep trying to open a file if it is inaccessible"). 75 | NewBool(false) 76 | 77 | t.SleepInterval = command.Opt("s, sleep-interval", "with -f, sleep for approximately N seconds\n"+ 78 | "(default 1.0) between iterations;\n"+ 79 | "with inotify and --pid=P, check process P at\n"+ 80 | "least once every N seconds"). 81 | Flags(flag.PosixShort). 82 | NewFloat64(1.0) 83 | 84 | t.Verbose = command.Opt("v, verbose", "always print headers giving file names"). 85 | Flags(flag.PosixShort). 86 | NewBool(false) 87 | 88 | zeroTerminated := command.Opt("z, zero-terminated", "line delimiter is NUL, not newline"). 89 | Flags(flag.PosixShort). 90 | NewBool(false) 91 | 92 | command.Parse(argv[1:]) 93 | 94 | t.LineDelim = '\n' 95 | if *zeroTerminated { 96 | t.LineDelim = '\000' 97 | } 98 | 99 | if *follow { 100 | f := "name" 101 | t.Follow = &f 102 | } 103 | 104 | args := command.Args() 105 | if len(args) == 0 { 106 | args = append(args, "-") 107 | } 108 | 109 | return &t, args 110 | } 111 | 112 | func (t *Tail) PrintBytes(rs io.ReadSeeker, w io.Writer) error { 113 | nBytes := 0 114 | 115 | n, err := utils.HeadParseSize(*t.Bytes) 116 | if err != nil { 117 | return err 118 | } 119 | 120 | nBytes = int(n) 121 | bytes := *t.Bytes 122 | 123 | readTail := false 124 | if bytes[0] != '+' || nBytes < 0 { 125 | if nBytes > 0 { 126 | nBytes = -nBytes 127 | } 128 | readTail = true 129 | } 130 | 131 | if readTail { 132 | offset, err := rs.Seek(0, 2) 133 | if err != nil { 134 | return err 135 | } 136 | 137 | rs.Seek(offset+int64(nBytes), 0) 138 | 139 | _, err = io.Copy(w, rs) 140 | return err 141 | } 142 | 143 | if nBytes > 0 { 144 | nBytes-- 145 | } 146 | 147 | if _, err = rs.Seek(int64(nBytes), 0); err != nil { 148 | return err 149 | } 150 | 151 | _, err = io.Copy(w, rs) 152 | 153 | return err 154 | } 155 | 156 | func (t *Tail) printTailLines(rs io.ReadSeeker, w io.Writer, n int) error { 157 | no := 0 158 | totalMap := map[int]int{} 159 | br := bufio.NewReader(rs) 160 | 161 | // todo 优化: 162 | // 直接定位到最后位置向前读取一个个buffer block 163 | // 计算 '\n' 个数, 输出行数, 可以优化大文件尾部读取 164 | for no = 0; ; no++ { 165 | 166 | l, e := br.ReadBytes(t.LineDelim) 167 | 168 | if e != nil && len(l) == 0 { 169 | no-- 170 | break 171 | } 172 | 173 | if no == 0 { 174 | totalMap[no] = len(l) 175 | continue 176 | } 177 | 178 | totalMap[no] = totalMap[no-1] + len(l) 179 | } 180 | 181 | rs.Seek(int64(totalMap[no+n]), 0) 182 | 183 | for { 184 | 185 | l, e := br.ReadBytes(t.LineDelim) 186 | if e != nil && len(l) == 0 { 187 | break 188 | } 189 | 190 | w.Write(l) 191 | } 192 | 193 | return nil 194 | } 195 | 196 | func (t *Tail) PrintLines(rs io.ReadSeeker, w io.Writer) error { 197 | nLines := 0 198 | 199 | n0, err := utils.HeadParseSize(*t.Lines) 200 | if err != nil { 201 | return err 202 | } 203 | 204 | nLines = int(n0) 205 | 206 | lines := *t.Lines 207 | readLast := true 208 | 209 | //+10 210 | if lines[0] != '+' && lines[0] != '-' || nLines < 0 { 211 | readLast = false 212 | if nLines > 0 { 213 | nLines = -nLines 214 | } 215 | } 216 | 217 | if readLast { 218 | 219 | br := bufio.NewReader(rs) 220 | for i := 1; ; i++ { 221 | 222 | l, e := br.ReadBytes(t.LineDelim) 223 | if e != nil && len(l) == 0 { 224 | break 225 | } 226 | 227 | if i < nLines { 228 | continue 229 | } 230 | 231 | w.Write(l) 232 | } 233 | return nil 234 | } 235 | 236 | return t.printTailLines(rs, w, nLines) 237 | } 238 | 239 | func (t *Tail) PrintTitle(w io.Writer, name string) { 240 | fmt.Fprintf(w, "==> %s <==\n", name) 241 | } 242 | 243 | func (t *Tail) FollowLoop(rs io.ReadSeeker, w io.Writer) { 244 | 245 | br := bufio.NewReader(rs) 246 | for { 247 | 248 | time.Sleep(time.Duration(float64(time.Second) * *t.SleepInterval)) 249 | 250 | for { 251 | 252 | l, e := br.ReadBytes(t.LineDelim) 253 | if e != nil && len(l) == 0 { 254 | break 255 | } 256 | w.Write(l) 257 | } 258 | } 259 | } 260 | 261 | func (t *Tail) main(rs io.ReadSeeker, w io.Writer, name string) { 262 | if *t.Verbose { 263 | t.PrintTitle(w, name) 264 | } 265 | 266 | if len(*t.Follow) > 0 { 267 | t.FollowLoop(rs, w) 268 | } 269 | 270 | if t.Bytes != nil && *t.Bytes != "0" { 271 | t.PrintBytes(rs, w) 272 | return 273 | } 274 | 275 | if t.Lines != nil && len(*t.Lines) > 0 { 276 | t.PrintLines(rs, w) 277 | return 278 | } 279 | 280 | } 281 | 282 | func Main(argv []string) { 283 | t, args := New(argv) 284 | 285 | for _, v := range args { 286 | fd, err := utils.OpenFile(v) 287 | if err != nil { 288 | utils.Die("head:%s\n", err) 289 | } 290 | 291 | t.main(fd, os.Stdout, v) 292 | fd.Close() 293 | } 294 | } 295 | -------------------------------------------------------------------------------- /tail/tail/tail.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/guonaihong/coreutils/tail" 5 | "os" 6 | ) 7 | 8 | func main() { 9 | tail.Main(os.Args) 10 | } 11 | -------------------------------------------------------------------------------- /tail/tail_test.go: -------------------------------------------------------------------------------- 1 | package tail 2 | 3 | import ( 4 | "bytes" 5 | "strings" 6 | "testing" 7 | ) 8 | 9 | func TestLines(t *testing.T) { 10 | tail := Tail{} 11 | 12 | one := "1\n" 13 | lines := `2 14 | 3 15 | 4 16 | 5 17 | 6 18 | 7 19 | 8 20 | 9 21 | 10 22 | 11` 23 | 24 | l := "10" 25 | tail.Lines = &l 26 | tail.LineDelim = '\n' 27 | 28 | rs := strings.NewReader(one + lines) 29 | w := &bytes.Buffer{} 30 | 31 | err := tail.PrintLines(rs, w) 32 | if w.String() != lines { 33 | t.Errorf("tail -n 10 fail (%s)\n", w.String()) 34 | } 35 | 36 | l = "-10" 37 | tail.Lines = &l 38 | rs.Seek(0, 0) 39 | w.Reset() 40 | 41 | err = tail.PrintLines(rs, w) 42 | if w.String() != lines { 43 | t.Errorf("tail -n -10 fail (%s)\n", w.String()) 44 | } 45 | 46 | if err != nil { 47 | t.Errorf("printLines %s\n", err) 48 | } 49 | 50 | lines = `1 51 | 2 52 | 3 53 | 4 54 | 5 55 | 6 56 | 7 57 | 8 58 | 9 59 | ` 60 | last := "10\n11" 61 | 62 | l = "+10" 63 | tail.Lines = &l 64 | rs = strings.NewReader(lines + last) 65 | w.Reset() 66 | err = tail.PrintLines(rs, w) 67 | 68 | if w.String() != last { 69 | t.Errorf("tail -n +10 fail (%s)\n", w.String()) 70 | } 71 | if err != nil { 72 | t.Errorf("printLines %s \n", err) 73 | } 74 | } 75 | 76 | func TestLinesPlus(t *testing.T) { 77 | tail := Tail{} 78 | lines := `1 79 | 2 80 | 3 81 | 4 82 | 5 83 | 6 84 | 7 85 | 8 86 | 9 87 | 10 88 | 11 89 | ` 90 | l := "+0" 91 | tail.Lines = &l 92 | rs := strings.NewReader(lines) 93 | w := &bytes.Buffer{} 94 | err := tail.PrintLines(rs, w) 95 | 96 | if w.String() != lines { 97 | t.Errorf("tail -n +0 fail (%s)\n", w.String()) 98 | } 99 | if err != nil { 100 | t.Errorf("printLines %s \n", err) 101 | } 102 | 103 | l = "+1" 104 | tail.Lines = &l 105 | rs = strings.NewReader(lines) 106 | w = &bytes.Buffer{} 107 | err = tail.PrintLines(rs, w) 108 | 109 | if w.String() != lines { 110 | t.Errorf("tail -n +0 fail (%s)\n", w.String()) 111 | } 112 | if err != nil { 113 | t.Errorf("printLines %s \n", err) 114 | } 115 | } 116 | 117 | func TestPrintBytes(t *testing.T) { 118 | testBytes := `123456789abcdef` 119 | 120 | tail := Tail{} 121 | rs := strings.NewReader(testBytes) 122 | w := &bytes.Buffer{} 123 | 124 | b := "1" 125 | tail.Bytes = &b 126 | 127 | err := tail.PrintBytes(rs, w) 128 | if w.String() != "f" { 129 | t.Errorf("tail -c 1 fail(%s)\n", w.String()) 130 | } 131 | 132 | if err != nil { 133 | t.Errorf("PrintBytes %s\n", err) 134 | } 135 | 136 | b = "-1" 137 | tail.Bytes = &b 138 | rs.Seek(0, 0) 139 | w.Reset() 140 | err = tail.PrintBytes(rs, w) 141 | if w.String() != "f" { 142 | t.Errorf("tail -c -1 (%s)\n", w.String()) 143 | } 144 | 145 | b = "0" 146 | tail.Bytes = &b 147 | rs.Seek(0, 0) 148 | w.Reset() 149 | 150 | err = tail.PrintBytes(rs, w) 151 | if w.String() != "" { 152 | t.Errorf("tail -c 0 fail(%s)\n", w.String()) 153 | } 154 | 155 | b = "-0" 156 | tail.Bytes = &b 157 | rs.Seek(0, 0) 158 | w.Reset() 159 | 160 | err = tail.PrintBytes(rs, w) 161 | if w.String() != "" { 162 | t.Errorf("tail -c -0 fail(%s)\n", w.String()) 163 | } 164 | } 165 | 166 | func TestPrintBytesPlus(t *testing.T) { 167 | testBytes := `123456789abcdef` 168 | rs := strings.NewReader(testBytes) 169 | w := &bytes.Buffer{} 170 | 171 | tail := Tail{} 172 | 173 | b := "+1" 174 | tail.Bytes = &b 175 | err := tail.PrintBytes(rs, w) 176 | if w.String() != "123456789abcdef" { 177 | t.Errorf("tail -c +1 fail(%s)\n", w.String()) 178 | } 179 | 180 | if err != nil { 181 | t.Errorf("PrintLines %s\n", err) 182 | } 183 | 184 | b = "+2" 185 | tail.Bytes = &b 186 | rs.Seek(0, 0) 187 | w.Reset() 188 | 189 | err = tail.PrintBytes(rs, w) 190 | if w.String() != "23456789abcdef" { 191 | t.Errorf("tail -c + 2 fail(%s)\n", w.String()) 192 | } 193 | 194 | if err != nil { 195 | t.Errorf("PrintLines %s\n", err) 196 | } 197 | 198 | b = "+0" 199 | tail.Bytes = &b 200 | rs.Seek(0, 0) 201 | w.Reset() 202 | 203 | err = tail.PrintBytes(rs, w) 204 | if w.String() != "123456789abcdef" { 205 | t.Errorf("tail -c +0 fail(%s)\n", err) 206 | } 207 | } 208 | -------------------------------------------------------------------------------- /tee/README.md: -------------------------------------------------------------------------------- 1 | # tee 2 | 3 | #### 简介 4 | tee 在完成gnu tee 功能命令基础上,并增强tee命令。 5 | 6 | #### install 7 | ```bash 8 | env GOPATH=`pwd` go get -u github.com/guonaihong/tee/tee 9 | ``` 10 | 11 | #### 命令行选项 12 | ```console 13 | Usage of tee: 14 | -A, --max-archive int 15 | How many archive files are saved 16 | -a, --append 17 | append to the given FILEs, do not overwrite 18 | -g, --gzip 19 | compressed archived log files 20 | -s, --max-size string 21 | current file maximum write size 22 | ``` 23 | 24 | #### 提供对日志文件的自动归档和裁减 25 | * loop.sh 内容 26 | ```bash 27 | while :; 28 | do 29 | date 30 | done 31 | ``` 32 | 33 | * tee命令保证始终只有10个归档文件(.gz), 超过1M自动归档 34 | ```bash 35 | bash loop.sh | tee -A 10 -ga my.log -s 1M 36 | ``` 37 | -------------------------------------------------------------------------------- /tee/tee.go: -------------------------------------------------------------------------------- 1 | package tee 2 | 3 | import ( 4 | "fmt" 5 | "github.com/guonaihong/flag" 6 | "github.com/guonaihong/gutil/file" 7 | "github.com/guonaihong/log" 8 | "io" 9 | "os" 10 | ) 11 | 12 | func Main(argv []string) { 13 | command := flag.NewFlagSet(argv[0], flag.ExitOnError) 14 | 15 | append := command.Opt("a, append", "append to the given FILEs, do not overwrite"). 16 | Flags(flag.PosixShort).NewBool(false) 17 | 18 | ignoreInterrupts := command.Opt("i, ignore-interrupts", "ignore interrupt signals"). 19 | Flags(flag.PosixShort).NewBool(false) 20 | 21 | gzip := command.Opt("g, gzip", "compressed archived log files"). 22 | Flags(flag.PosixShort).NewBool(false) 23 | 24 | maxSize := command.Opt("s, max-size", "current file maximum write size"). 25 | NewString("0") 26 | 27 | maxArchive := command.Opt("A, max-archive", "How many archive files are saved"). 28 | NewInt64(0) 29 | 30 | command.Parse(argv[1:]) 31 | 32 | var fileArch *log.File 33 | var buffer [4096]byte 34 | var fileName string 35 | var w io.Writer 36 | 37 | if *ignoreInterrupts { 38 | ignore() 39 | } 40 | 41 | args := command.Args() 42 | if len(args) == 0 { 43 | w = os.Stdout 44 | goto end 45 | } 46 | 47 | fileName = args[0] 48 | if *maxSize != "" { 49 | size, err := file.ParseSize(*maxSize) 50 | if err != nil { 51 | fmt.Printf("%s\n", err) 52 | os.Exit(1) 53 | return 54 | } 55 | 56 | var compress log.CompressType 57 | compress = log.NotCompress 58 | if *gzip { 59 | compress = log.Gzip 60 | } 61 | 62 | fileArch = log.NewFile("", fileName, compress, int(size), int(*maxArchive)) 63 | 64 | w = fileArch 65 | } else { 66 | isAppend := 0 67 | if *append { 68 | isAppend = os.O_APPEND 69 | } 70 | 71 | file, err := os.OpenFile(fileName, os.O_CREATE|os.O_WRONLY|isAppend, 0644) 72 | if err != nil { 73 | fmt.Printf("%s\n", err) 74 | } 75 | w = file 76 | } 77 | 78 | end: 79 | for { 80 | n, err := os.Stdin.Read(buffer[:]) 81 | if err != nil { 82 | break 83 | } 84 | w.Write(buffer[:n]) 85 | if len(args) != 0 { 86 | os.Stdout.Write(buffer[:n]) 87 | } 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /tee/tee/tee.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/guonaihong/coreutils/tee" 5 | "os" 6 | ) 7 | 8 | func main() { 9 | tee.Main(os.Args) 10 | } 11 | -------------------------------------------------------------------------------- /tee/tee_darwin.go: -------------------------------------------------------------------------------- 1 | package tee 2 | 3 | import ( 4 | "os/signal" 5 | "syscall" 6 | ) 7 | 8 | func ignore() { 9 | signal.Ignore(syscall.SIGABRT) 10 | signal.Ignore(syscall.SIGALRM) 11 | signal.Ignore(syscall.SIGBUS) 12 | signal.Ignore(syscall.SIGCHLD) 13 | 14 | signal.Ignore(syscall.SIGCONT) 15 | signal.Ignore(syscall.SIGFPE) 16 | signal.Ignore(syscall.SIGHUP) 17 | signal.Ignore(syscall.SIGILL) 18 | signal.Ignore(syscall.SIGINT) 19 | signal.Ignore(syscall.SIGIO) 20 | signal.Ignore(syscall.SIGIOT) 21 | signal.Ignore(syscall.SIGKILL) 22 | signal.Ignore(syscall.SIGPIPE) 23 | signal.Ignore(syscall.SIGPROF) 24 | signal.Ignore(syscall.SIGQUIT) 25 | signal.Ignore(syscall.SIGSEGV) 26 | signal.Ignore(syscall.SIGSTOP) 27 | signal.Ignore(syscall.SIGSYS) 28 | signal.Ignore(syscall.SIGTERM) 29 | signal.Ignore(syscall.SIGTRAP) 30 | //signal.Ignore(syscall.SIGTSTP) //ctrl-z 31 | signal.Ignore(syscall.SIGTTIN) 32 | signal.Ignore(syscall.SIGTTOU) 33 | signal.Ignore(syscall.SIGURG) 34 | signal.Ignore(syscall.SIGUSR1) 35 | signal.Ignore(syscall.SIGUSR2) 36 | signal.Ignore(syscall.SIGVTALRM) 37 | signal.Ignore(syscall.SIGWINCH) 38 | signal.Ignore(syscall.SIGXCPU) 39 | signal.Ignore(syscall.SIGXFSZ) 40 | } 41 | -------------------------------------------------------------------------------- /tee/tee_linux.go: -------------------------------------------------------------------------------- 1 | package tee 2 | 3 | import ( 4 | "os/signal" 5 | "syscall" 6 | ) 7 | 8 | func ignore() { 9 | signal.Ignore(syscall.SIGABRT) 10 | signal.Ignore(syscall.SIGALRM) 11 | signal.Ignore(syscall.SIGBUS) 12 | signal.Ignore(syscall.SIGCHLD) 13 | 14 | signal.Ignore(syscall.SIGCLD) 15 | signal.Ignore(syscall.SIGPOLL) 16 | signal.Ignore(syscall.SIGPWR) 17 | 18 | signal.Ignore(syscall.SIGCONT) 19 | signal.Ignore(syscall.SIGFPE) 20 | signal.Ignore(syscall.SIGHUP) 21 | signal.Ignore(syscall.SIGILL) 22 | signal.Ignore(syscall.SIGINT) 23 | signal.Ignore(syscall.SIGIO) 24 | signal.Ignore(syscall.SIGIOT) 25 | signal.Ignore(syscall.SIGKILL) 26 | signal.Ignore(syscall.SIGPIPE) 27 | signal.Ignore(syscall.SIGPROF) 28 | signal.Ignore(syscall.SIGQUIT) 29 | signal.Ignore(syscall.SIGSEGV) 30 | signal.Ignore(syscall.SIGSTKFLT) 31 | signal.Ignore(syscall.SIGSTOP) 32 | signal.Ignore(syscall.SIGSYS) 33 | signal.Ignore(syscall.SIGTERM) 34 | signal.Ignore(syscall.SIGTRAP) 35 | //signal.Ignore(syscall.SIGTSTP) //ctrl-z 36 | signal.Ignore(syscall.SIGTTIN) 37 | signal.Ignore(syscall.SIGTTOU) 38 | signal.Ignore(syscall.SIGUNUSED) 39 | signal.Ignore(syscall.SIGURG) 40 | signal.Ignore(syscall.SIGUSR1) 41 | signal.Ignore(syscall.SIGUSR2) 42 | signal.Ignore(syscall.SIGVTALRM) 43 | signal.Ignore(syscall.SIGWINCH) 44 | signal.Ignore(syscall.SIGXCPU) 45 | signal.Ignore(syscall.SIGXFSZ) 46 | } 47 | -------------------------------------------------------------------------------- /touch/README.md: -------------------------------------------------------------------------------- 1 | # touch 2 | 3 | #### summary 4 | touch has the same touch command as ubuntu 18.04 5 | 6 | #### install 7 | ``` 8 | env GOPATH=`pwd` go get -u github.com/guonaihong/coreutils/touch/touch 9 | ``` 10 | 11 | #### usage 12 | ```console 13 | Usage of touch: 14 | -a change only the access time 15 | -c, --no-create 16 | do not create any files 17 | -d, --date string 18 | parse STRING and use it instead of current time 19 | -debug 20 | debug mode 21 | -h, --no-dereference 22 | affect each symbolic link instead of any referenced 23 | file (useful only on systems that can change the 24 | timestamps of a symlink) 25 | -m change only the modification time 26 | -r, --referenced string 27 | use this file's times instead of current time 28 | -t string 29 | use [[CC]YY]MMDDhhmm[.ss] instead of current time 30 | -time string 31 | change the specified time: 32 | WORD is access, atime, or use: equivalent to -a 33 | WORD is modify or mtime: equivalent to -m 34 | ``` 35 | 36 | #### todo 37 | gnu touch -d 选项是用yacc实现,如果要复该投入时间巨大,后面实现 38 | -------------------------------------------------------------------------------- /touch/hello.txt: -------------------------------------------------------------------------------- 1 | hello 2 | -------------------------------------------------------------------------------- /touch/touch/touch.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/guonaihong/coreutils/touch" 5 | "os" 6 | ) 7 | 8 | func main() { 9 | touch.Main(os.Args) 10 | } 11 | -------------------------------------------------------------------------------- /touch/touch_test.go: -------------------------------------------------------------------------------- 1 | package touch 2 | 3 | import ( 4 | "github.com/guonaihong/coreutils/utils" 5 | "os" 6 | "testing" 7 | "time" 8 | ) 9 | 10 | const fileName1 = "touch-test-file1" 11 | const fileName2 = "touch-test-file2" 12 | 13 | func testParseTime(tm string, needTm time.Time, t *testing.T) { 14 | touch := Touch{} 15 | //touch.debug = utils.Bool(true) 16 | t1, err := touch.parseTime(tm) 17 | if err != nil { 18 | t.Errorf("touch -t fail:%s\n", err) 19 | } 20 | 21 | if !t1.Equal(needTm) { 22 | t.Errorf("needTm(%v), tm(%v)\n", needTm, t1) 23 | } 24 | } 25 | 26 | func TestParseTime(t *testing.T) { 27 | now := time.Now() 28 | //15 29 | testParseTime("201212101830.55", time.Date(2012, 12, 10, 18, 30, 55, 0, time.UTC), t) 30 | 31 | //12 32 | testParseTime("201812101730", time.Date(2018, 12, 10, 17, 30, 00, 0, time.UTC), t) 33 | 34 | //13 35 | testParseTime("1712101730.44", time.Date(2017, 12, 10, 17, 30, 44, 0, time.UTC), t) 36 | testParseTime("6712101730.44", time.Date(2067, 12, 10, 17, 30, 44, 0, time.UTC), t) 37 | 38 | //10 39 | testParseTime("1712101730", time.Date(2017, 12, 10, 17, 30, 00, 0, time.UTC), t) 40 | 41 | //11 42 | testParseTime("12101730.33", time.Date(now.Year(), 12, 10, 17, 30, 33, 0, time.UTC), t) 43 | 44 | //8 45 | testParseTime("12101730", time.Date(now.Year(), 12, 10, 17, 30, 0, 0, time.UTC), t) 46 | } 47 | 48 | func testParseDate(date string, needDate time.Time, t *testing.T) { 49 | touch := Touch{} 50 | t1, err := touch.parseDate(date) 51 | if err != nil { 52 | t.Errorf("touch -d fail:%s\n", err) 53 | } 54 | 55 | if !t1.Equal(needDate) { 56 | t.Errorf("needDate(%v), date(%v)\n", needDate, t1) 57 | } 58 | } 59 | 60 | func TestParseDate(t *testing.T) { 61 | now := time.Now() 62 | testParseDate("1 May 2005 10:22", time.Date(2005, 5, 1, 10, 22, 0, 0, time.UTC), t) 63 | testParseDate("14 May", time.Date(now.Year(), 5, 14, 0, 0, 0, 0, time.UTC), t) 64 | testParseDate("14:24", time.Date(now.Year(), now.Month(), now.Day(), 14, 24, 0, 0, time.UTC), t) 65 | } 66 | 67 | //touch -c file 68 | func TestCreateFileNoCreate(t *testing.T) { 69 | touch := Touch{} 70 | touch.NoCreate = utils.Bool(true) 71 | 72 | touch.Touch(fileName1) 73 | if !isNotExist(fileName1) { 74 | t.Errorf("touch -c found file %s\n", fileName1) 75 | } 76 | } 77 | 78 | //touch file 79 | func TestCreateFile(t *testing.T) { 80 | touch := Touch{} 81 | touch.Touch(fileName2) 82 | defer os.Remove(fileName2) 83 | 84 | if isNotExist(fileName2) { 85 | t.Errorf("not found %s\n", fileName2) 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /tr/README.md: -------------------------------------------------------------------------------- 1 | # tr 2 | 3 | #### 简介 4 | tr 使用gnu的命令行选项 5 | 6 | #### install 7 | ``` 8 | env GOPATH=`pwd` go get -u github.com/guonaihong/coreutils/tr/tr 9 | ``` 10 | 11 | #### Example 12 | (来自linux shell cookbook) 13 | tr只能通过stdin接收输入。调用格式如下: 14 | tr [options] set1 set2 15 | 来自stdin的输入字符会按照位置从set1映射到set2,然后将输出写入stdout。set1和set2是字符类或字符组。 16 | 如果两个字符组的长度不相等,那么set2会不断复制其最后一个字符,直到长度与set1相同。 17 | 如果set2的长度大于set1,那么在set2中超出set1长度的那部分字符则全部被忽略。 18 | 19 | 要将输入中的字符由大写转换成小写,可以使用 20 | ```shell 21 | echo "HELLO WORLD" |tr 'A-Z' 'a-z' 22 | ``` 23 | 'A-Z'和'a-z'都是字符组。我们可以按照需要追加字符或字符类来构造自己的字符组。 24 | 'ABD-}'、'aA., '、'a-ce-x'以及'a-c0-9'等均是合法的集合。定义集合也很简单,不需要书写一长串连续的字符序列, 25 | 只需要使用“起始字符-终止字符”这种格式就行了。这种写法也可以和其他字符或者字符类结合使用。如果“起始字符-终止字符” 26 | 不是有效连续字符序列,那么它就会被视为含有3个元素的集合(起始字符、-和终止字符)。你也可以使用像'\t'、'\n'这种特殊字符或者其他ASCII字符 27 | 28 | 在tr中利用集合的概念,可以轻松地将字符从一个集合映射到另一个集合中。 29 | ```shell 30 | echo 12345|tr '0-9' '9876543210' 31 | 87654 32 | ``` 33 | ```shell 34 | echo 87654|tr '9876543210' '0-9' 35 | ``` 36 | tr命令可以用来加密。ROT13是一个著名的加密算法。在ROT13算法中,字符会被移动13个位置,因此文本加密和解密都使用相同一个函数 37 | ```shell 38 | echo "tr came, tr saw, tr conquered."|tr 'a-zA-Z' 'n-za-mN-ZA-M' 39 | 40 | output: 41 | ge pnzr, ge fnj, ge pbadhrerq. 42 | ``` 43 | 还可以将制表符转换成单个空格: 44 | ```shell 45 | tr '\t' ' ' < file.txt 46 | ``` 47 | ##### 用tr删除字符 48 | tr有一个选项-d,可以通过指定需要被删除的字符集合,将出现在stdin中的特定字符清除掉: 49 | ```shell 50 | cat file.txt | tr -d '[set1]' 51 | #只使用set1,不使用set2 52 | ``` 53 | 例如: 54 | ```shell 55 | echo "Hello 123 world 456"|tr -d '0-9' 56 | Hello world 57 | ``` 58 | 将stdin数字删除并打印删除后的结果 59 | ##### 字符组补集 60 | 可以利用选项-c来使用set1的补集。下面的命令中,set2是可选的: 61 | ```shell 62 | tr -c [set1] [set2] 63 | ``` 64 | 如果只给出了set1,那么tr会删除所有不在set1中的字符。如果也给出了set2,tr会将不在set1中的字符转换成set2中的字符。 65 | 如果使用了-c选项,set1和set2必须都给出。如果-c与-d选项同时出现,你只能使用set1,其他所有的字符都会被删除。 66 | 下面的例子会从输入文本中删除不在补集中的所有字符: 67 | ```shell 68 | echo hello 1 char 2 next 4|tr -d -c '0-9\n' 69 | 124 70 | ``` 71 | 接下来的例子会将不在set1中的字符替换成空格 72 | ```shell 73 | echo hello 1 char 2 next 4| tr -c '0-9' ' ' 74 | ``` 75 | 1 2 4 76 | ##### 用tr压缩字符 77 | tr命令能够完成很多文本处理任务。例如,它可以删除字符串中重复出现的字符。基本实现形式如下: 78 | ```shell 79 | tr -s '[需要被压缩的一组字符]' 80 | ``` 81 | 如果你习惯在点号后面放置两个空格,你需要在不删除重复字母的情况下去掉多余的空格: 82 | ``` 83 | echo "GNU is not UNIX. Recursive right ?"|tr -s ' ' 84 | ``` 85 | tr命令还可以用来删除多余的换行符: 86 | ``` 87 | cat multi_blanks.txt 88 | hello 89 | 90 | 91 | 92 | 93 | 94 | world 95 | # 96 | 97 | 98 | 99 | 100 | 我爱中国 101 | cat multi_blanks.txt|tr -s '\n' 102 | hello 103 | world 104 | # 105 | 我爱中国 106 | ``` 107 | 上面的例子展示了如何使用tr删除多余的'\n'字符。接下来让我们用一种巧妙的方式将数字列表进行相加 108 | ``` 109 | seq 10|echo $[ `tr '\n' '+' ` 0] 110 | ``` 111 | 112 | -s 支持两个集合 113 | ``` 114 | echo -e "aaaaaaaaaaaaaaaaaaaaaaaaaa1 \n\n\nbbbbbbbb2" |tr -s "a-z" "0-9" 115 | ``` 116 | 117 | -s -d组合使用 118 | ``` 119 | echo -e "aaaaaaaaaaaaaaaaaaaaaaaaaa1 \n\n\nbbbbbbbb2" |tr -s -d "a-z" "0-9" 120 | ``` 121 | ``` 122 | echo "`echo -n {0..9}``echo -n {a..z}`" |tr -d " " |tr -d -s "0-9" "a-c" 123 | ``` 124 | 125 | -c使用 126 | ``` 127 | echo -n "0 1 2 3 4 5 6 7 8 9 a b c d e f g h i j k l m n o p q r s t u v w x y z"|tr -d " " |tr -c "0-9" "a-c" 128 | output: 129 | 0123456789cccccccccccccccccccccccccc 130 | ``` 131 | ##### 字符类 132 | tr可以将不同的字符类作为集合使用,所支持的字符类如下所示。 133 | * alnum: 字母和数字 134 | * alpha: 字母 135 | * cntrl: 控制(非打印)字符 136 | * digit: 数字 137 | * graph: 图形字符 138 | * lower: 小写字母 139 | * print: 可打印字符 140 | * punct: 标点符号 141 | * space: 空白字符 142 | * upper: 大写字母 143 | * xdigit: 十六进制字符 144 | 可以按照下面的方式选择所需的字符类 145 | tr [:class:] [:class:] 146 | -------------------------------------------------------------------------------- /tr/tr/tr.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/guonaihong/coreutils/tr" 5 | "os" 6 | ) 7 | 8 | func main() { 9 | tr.Main(os.Args) 10 | } 11 | -------------------------------------------------------------------------------- /tr/tr_test.go: -------------------------------------------------------------------------------- 1 | package tr 2 | 3 | import ( 4 | "bytes" 5 | "strings" 6 | "testing" 7 | ) 8 | 9 | func testSet(dst, src string, set1, set2 string, test *testing.T) { 10 | t := Tr{} 11 | t.Init(set1, set2) 12 | rs := strings.NewReader(src) 13 | w := &bytes.Buffer{} 14 | 15 | t.Tr(rs, w) 16 | 17 | if w.String() != dst { 18 | test.Errorf("tr %s %s fail, (%s)need:%s\n", set1, set2, w.String(), dst) 19 | } 20 | } 21 | 22 | func TestSet(t *testing.T) { 23 | src := `HELLO WORLD` 24 | dst := `hello world` 25 | 26 | testSet(dst, src, "A-Z", "a-z", t) 27 | 28 | src = `12345` 29 | dst = `87654` 30 | 31 | testSet(dst, src, "0-9", "9876543210", t) 32 | 33 | testSet(src, dst, "9876543210", "0-9", t) 34 | 35 | src = `tr came, tr saw, tr conquered.` 36 | dst = `ge pnzr, ge fnj, ge pbadhrerq.` 37 | testSet(dst, src, "a-zA-Z", "n-za-mN-ZA-M", t) 38 | 39 | src = `WELCOME TO 40 | shanghai` 41 | dst = `WELCOME TO 42 | SHANGHAI` 43 | testSet(dst, src, "[a-z]", "[A-Z]", t) 44 | 45 | testSet(dst, src, "[:lower:]", "[:upper:]", t) 46 | 47 | src = `Welcome To shanghai` 48 | dst = `Welcome To shanghai` 49 | testSet(dst, src, "[:space:]", `\t`, t) 50 | 51 | src = `{WELCOME TO}` 52 | dst = `(WELCOME TO)` 53 | testSet(dst, src, "{}", "()", t) 54 | } 55 | 56 | func testDelete(dst, src string, set1, set2 string, delete bool, test *testing.T) { 57 | t := Tr{} 58 | t.Delete = delete 59 | t.Init(set1, set2) 60 | rs := strings.NewReader(src) 61 | w := &bytes.Buffer{} 62 | 63 | t.Tr(rs, w) 64 | 65 | if w.String() != dst { 66 | test.Errorf("tr -d %s fail, (%s)need:%s\n", set1, w.String(), dst) 67 | } 68 | } 69 | 70 | func TestDelete(t *testing.T) { 71 | src := `welcome` 72 | dst := `elcome` 73 | 74 | testDelete(dst, src, "w", "", true, t) 75 | 76 | src = "my ID is 73535" 77 | dst = "my ID is " 78 | 79 | testDelete(dst, src, "[:digit:]", "", true, t) 80 | } 81 | 82 | func testComplement(dst, src string, set1, set2 string, complement bool, test *testing.T) { 83 | t := Tr{} 84 | t.Complement = complement 85 | t.Init(set1, set2) 86 | rs := strings.NewReader(src) 87 | w := &bytes.Buffer{} 88 | 89 | t.Tr(rs, w) 90 | 91 | if w.String() != dst { 92 | test.Errorf("tr -d %s fail, (%s)need:%s\n", set1, w.String(), dst) 93 | } 94 | } 95 | 96 | func TestComplement(t *testing.T) { 97 | src := `unix` 98 | dst := `uaaa` 99 | 100 | testComplement(dst, src, "u", "a", true, t) 101 | } 102 | -------------------------------------------------------------------------------- /true/README.md: -------------------------------------------------------------------------------- 1 | # true 2 | 3 | #### install 4 | ``` 5 | env GOPATH=`pwd` go get -u github.com/guonaihong/coreutils/true/true 6 | ``` 7 | -------------------------------------------------------------------------------- /true/true.go: -------------------------------------------------------------------------------- 1 | package true 2 | 3 | func Main(argv []string) { 4 | } 5 | -------------------------------------------------------------------------------- /true/true/true.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "github.com/guonaihong/coreutils/true" 4 | 5 | func main() { 6 | true.Main([]string{}) 7 | } 8 | -------------------------------------------------------------------------------- /uname/README.md: -------------------------------------------------------------------------------- 1 | # uname 2 | 3 | #### summary 4 | uname has the same uname command as ubuntu 18.04 5 | 6 | #### install 7 | ``` 8 | env GOPATH=`pwd` go get -u github.com/guonaihong/coreutils/uname/uname 9 | ``` 10 | 11 | #### usage 12 | ```console 13 | Usage of uname: 14 | -a, --all 15 | print all information, in the following order, 16 | except omit -p and -i if unknown: 17 | -i, --hardware-platform 18 | print the hardware platform (non-portable) 19 | -m, --machine 20 | print the machine hardware name 21 | -n, --nodename 22 | print the network node hostname 23 | -o, --operating-system 24 | print the operating system 25 | -p, --processor 26 | print the processor type (non-portable) 27 | -r, --kernel-release 28 | print the kernel release 29 | -s, --kernel-name 30 | print the kernel name 31 | -v, --kernel-version 32 | print the kernel version 33 | ``` 34 | -------------------------------------------------------------------------------- /uname/uname.go: -------------------------------------------------------------------------------- 1 | package uname 2 | 3 | import ( 4 | "bytes" 5 | _ "fmt" 6 | "github.com/guonaihong/flag" 7 | "golang.org/x/sys/unix" 8 | "io" 9 | "os" 10 | "runtime" 11 | ) 12 | 13 | type Uname struct { 14 | KernelName bool 15 | NodeName bool 16 | KernelRelease bool 17 | KernelVersion bool 18 | Machine bool 19 | Processor bool 20 | HardwarePlatform bool 21 | OperatingSystem bool 22 | count int 23 | } 24 | 25 | type utsname struct { 26 | Sysname []byte 27 | Nodename []byte 28 | Release []byte 29 | Version []byte 30 | Machine []byte 31 | OperatingSystem []byte 32 | } 33 | 34 | func New(argv []string) (*Uname, []string) { 35 | u := &Uname{} 36 | 37 | command := flag.NewFlagSet(argv[0], flag.ExitOnError) 38 | 39 | all := command.Opt("a, all", "print all information, in the following order,\n"+ 40 | "except omit -p and -i if unknown:"). 41 | Flags(flag.PosixShort).NewBool(false) 42 | 43 | command.Opt("s, kernel-name", "print the kernel name"). 44 | Flags(flag.PosixShort).Var(&u.KernelName) 45 | 46 | command.Opt("n, nodename", "print the network node hostname"). 47 | Flags(flag.PosixShort).Var(&u.NodeName) 48 | 49 | command.Opt("r, kernel-release", "print the kernel release"). 50 | Flags(flag.PosixShort).Var(&u.KernelRelease) 51 | 52 | command.Opt("v, kernel-version", "print the kernel version"). 53 | Flags(flag.PosixShort).Var(&u.KernelVersion) 54 | 55 | command.Opt("m, machine", "print the machine hardware name"). 56 | Flags(flag.PosixShort).Var(&u.Machine) 57 | 58 | command.Opt("p, processor", "print the processor type (non-portable)"). 59 | Flags(flag.PosixShort).Var(&u.Processor) 60 | 61 | command.Opt("i, hardware-platform", "print the hardware platform (non-portable)"). 62 | Flags(flag.PosixShort).Var(&u.HardwarePlatform) 63 | 64 | command.Opt("o, operating-system", "print the operating system"). 65 | Flags(flag.PosixShort).Var(&u.OperatingSystem) 66 | 67 | command.Parse(argv[1:]) 68 | 69 | if all != nil && *all { 70 | u.KernelName = true 71 | u.NodeName = true 72 | u.KernelRelease = true 73 | u.KernelVersion = true 74 | u.Machine = true 75 | u.Processor = true 76 | u.HardwarePlatform = true 77 | u.OperatingSystem = true 78 | } 79 | 80 | return u, command.Args() 81 | 82 | } 83 | 84 | func truncated0Bytes(b []byte) []byte { 85 | pos := bytes.IndexByte(b, 0) 86 | if pos == -1 { 87 | return b 88 | } 89 | return b[:pos] 90 | } 91 | 92 | func (u *Uname) shouldBindUname(name *utsname) { 93 | buf := unix.Utsname{} 94 | 95 | unix.Uname(&buf) 96 | 97 | try: 98 | if u.KernelName { 99 | name.Sysname = truncated0Bytes(buf.Sysname[:]) 100 | u.count++ 101 | } 102 | 103 | if u.NodeName { 104 | name.Nodename = truncated0Bytes(buf.Nodename[:]) 105 | u.count++ 106 | } 107 | 108 | if u.KernelRelease { 109 | name.Release = truncated0Bytes(buf.Release[:]) 110 | u.count++ 111 | } 112 | 113 | if u.KernelVersion { 114 | name.Version = truncated0Bytes(buf.Version[:]) 115 | u.count++ 116 | } 117 | 118 | name.Machine = truncated0Bytes(buf.Machine[:]) 119 | 120 | if u.Machine { 121 | u.count++ 122 | } 123 | 124 | if u.HardwarePlatform { 125 | u.count++ 126 | } 127 | 128 | if u.Processor { 129 | u.count++ 130 | } 131 | 132 | if u.OperatingSystem { 133 | name.OperatingSystem = getOsName() 134 | u.count++ 135 | } 136 | 137 | if u.count == 0 { 138 | u.KernelName = true 139 | goto try 140 | } 141 | } 142 | 143 | func (u *Uname) writeSpace(w io.Writer) { 144 | if u.count--; u.count > 0 { 145 | w.Write([]byte{' '}) 146 | } 147 | } 148 | 149 | func (u *Uname) Uname(w io.Writer) { 150 | var name utsname 151 | 152 | u.shouldBindUname(&name) 153 | 154 | needLine := u.count > 0 155 | 156 | if u.KernelName { 157 | w.Write(name.Sysname) 158 | u.writeSpace(w) 159 | } 160 | 161 | if u.NodeName { 162 | w.Write(name.Nodename) 163 | u.writeSpace(w) 164 | } 165 | 166 | if u.KernelRelease { 167 | w.Write(name.Release) 168 | u.writeSpace(w) 169 | } 170 | 171 | if u.KernelVersion { 172 | w.Write(name.Version) 173 | u.writeSpace(w) 174 | } 175 | 176 | if u.Machine { 177 | w.Write(name.Machine) 178 | u.writeSpace(w) 179 | } 180 | 181 | if u.Processor { 182 | w.Write(name.Machine) 183 | u.writeSpace(w) 184 | } 185 | 186 | if u.HardwarePlatform { 187 | w.Write(name.Machine) 188 | u.writeSpace(w) 189 | } 190 | 191 | if u.OperatingSystem { 192 | w.Write(getOsName()) 193 | u.writeSpace(w) 194 | } 195 | 196 | if needLine { 197 | w.Write([]byte{'\n'}) 198 | } 199 | } 200 | 201 | func getOsName() []byte { 202 | osName := runtime.GOOS 203 | switch osName { 204 | case "linux": 205 | osName = "GNU/Linux" 206 | case "windows": 207 | osName = "Windows NT" 208 | case "freebsd": 209 | osName = "FreeBSD" 210 | case "openbsd": 211 | osName = "OpenBSD" 212 | case "darwin": 213 | osName = "Darwin" 214 | case "android": 215 | osName = "Android" 216 | } 217 | 218 | return []byte(osName) 219 | } 220 | 221 | func Main(argv []string) { 222 | u, _ := New(argv) 223 | 224 | u.Uname(os.Stdout) 225 | } 226 | -------------------------------------------------------------------------------- /uname/uname/uname.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/guonaihong/coreutils/uname" 5 | "os" 6 | ) 7 | 8 | func main() { 9 | uname.Main(os.Args) 10 | } 11 | -------------------------------------------------------------------------------- /uniq/README.md: -------------------------------------------------------------------------------- 1 | # uniq 2 | 3 | #### 简介 4 | uniq 5 | 6 | #### install 7 | ``` 8 | env GOPATH=`pwd` go get -u github.com/guonaihong/coreutils/uniq/uniq 9 | ``` 10 | 11 | #### diff 12 | 与gnu uniq差异部分,相比gnu uniq,本实现不依赖sort命令,可以实现对文件行的去重操作,或者摘出独一无二行,或者重复行。 13 | 14 | #### usage 15 | 16 | ```console 17 | Usage of ./uniq: 18 | -D print all duplicate lines 19 | -all-repeated string 20 | like -D, but allow separating groups with an empty line; METHOD={none(default),prepend,separate} 21 | -c, --count 22 | prefix lines by the number of occurrences 23 | -d, --repeated 24 | only print duplicate lines 25 | -f, --skip-fields int 26 | avoid comparing the first N fields (default -2147483648) 27 | -group string 28 | show all items, separating groups with an empty line; METHOD={separate(default),prepend,append,both} 29 | -i, --ignore-case 30 | ignore differences in case when comparing 31 | -s, --skip-chars int 32 | avoid comparing the first N characters (default -2147483648) 33 | -u, --unique 34 | only print unique lines 35 | -w, --check-chars int 36 | compare no more than N characters in lines (default -2147483648) 37 | -z, --zero-terminated 38 | end lines with 0 byte, not newline 39 | ``` 40 | #### Example 41 | (下面的示例来自linux shell scripting cookbook) 和本人构造的测试用例 42 | 43 | uniq只能作用于排过序的数据,因此,uniq通常都与sort命令结合使用。 44 | ``` 45 | cat sorted.txt 46 | bash 47 | foss 48 | hack 49 | ``` 50 | 去重 51 | ``` 52 | sort unsorted.txt|uniq 53 | 或者 54 | sort unsorted.txt|uniq -u 55 | ``` 56 | 57 | 要统计各行在文件中出现的次数, 使用下面的命令 58 | ``` 59 | sort unsorted.txt|uniq -c 60 | 1 bash 61 | 1 foss 62 | 1 hack 63 | ``` 64 | 找出文件中重复的行 65 | ``` 66 | sort unsorted.txt|uniq -d 67 | hack 68 | ``` 69 | 70 | 我们可以结合-s 和 -w选项来指定键 71 | * -s 指定跳过前N个字符 72 | * -w指定用于比较的最大字符数 73 | 74 | 这个对比健可以作为uniq操作时的索引 75 | ``` 76 | cat uniq.txt 77 | u:01:gnu 78 | d:04:linux 79 | u:01:bash 80 | u:01:hack 81 | ``` 82 | 83 | 为了只测试指定的字符(铁略前两个字符,使用接下来的两个字符),我们使用-s 2跳过前两个字符,使用-w 2选项指定后续的两个字符: 84 | ``` 85 | sort uniq.txt | ./uniq -s 2 -w 2 86 | d:04:linux 87 | u:01:bash 88 | ``` 89 | 90 | 我们将命令输出作为xargs命令的输入时,最好为输出的各行添加一个0值字符终止符,使用uniq命令的输入作为xargs的数据源时, 91 | 同样应当如此。如果没有使用0值字节终止符,那么在默认情况下,xargs命令会用空格来分割参数。例如,来自stdin的文本行"this is a line" 92 | 会被xargs视为4个不同的参数。如果使用0值字节终止符,那么\0就被作为定界符,此时,包含空格的行就能够被正确的解析为单个参数。 93 | -z 选项可以生成由0值字节终止的输出 94 | ``` 95 | uniq -z file.txt 96 | ``` 97 | 98 | 下面的命令将删除所有指定的文件,这些文件的名字是从files.txt中读取的。 99 | ``` 100 | uniq -z file.txt | xargs -0 rm 101 | ``` 102 | 如果某个文件名出现多次,uniq命令只会将这个文件名写入stdout一次,这样就可以避免出现rm: cannot remove FILENAME: No such file or directory 103 | 104 | 测试 -all-repeated=prepend选项 105 | ``` 106 | echo -e "\naa\naa\ndd\n11\nbb\nbb\ncc\ncc\nff"|LC_ALL=C ./uniq --all-repeated=prepend 107 | 108 | aa 109 | aa 110 | 111 | bb 112 | bb 113 | 114 | cc 115 | cc 116 | ``` 117 | 118 | 测试 -all-repeated=separate选项 119 | ``` 120 | echo -e "aa\naa\ndd\n11\nbb\nbb\ncc\ncc\nff"|LC_ALL=C ./uniq --all-repeated=separate 121 | aa 122 | aa 123 | 124 | bb 125 | bb 126 | 127 | cc 128 | cc 129 | ``` 130 | 131 | 测试 --group=separate 132 | ``` 133 | echo -e "222\n222\n111\n111\n333\naaa\n" |./uniq --group=separate|cat -n 134 | 1 222 135 | 2 222 136 | 3 137 | 4 111 138 | 5 111 139 | 6 140 | 7 333 141 | 8 142 | 9 aaa 143 | 10 144 | 11 145 | 146 | ``` 147 | 148 | 测试 --group=prepend 149 | ``` 150 | echo -e "222\n222\n111\n111\n333\naaa\n" |./uniq --group=prepend|cat -n 151 | 1 152 | 2 222 153 | 3 222 154 | 4 155 | 5 111 156 | 6 111 157 | 7 158 | 8 333 159 | 9 160 | 10 aaa 161 | 11 162 | 12 163 | ``` 164 | 测试 --group=both 165 | ``` 166 | echo -e "222\n222\n111\n111\n333\naaa\n" |./uniq --group=both|cat -n 167 | 1 168 | 2 222 169 | 3 222 170 | 4 171 | 5 111 172 | 6 111 173 | 7 174 | 8 333 175 | 9 176 | 10 aaa 177 | 11 178 | 12 179 | 13 180 | 181 | ``` 182 | 测试 --group=append 183 | ``` 184 | echo -e "222\n222\n111\n111\n333\naaa\n" |./uniq --group=append|cat -n 185 | 1 222 186 | 2 222 187 | 3 188 | 4 111 189 | 5 111 190 | 6 191 | 7 333 192 | 8 193 | 9 aaa 194 | 10 195 | 11 196 | 12 197 | 198 | ``` 199 | -------------------------------------------------------------------------------- /uniq/uniq.go: -------------------------------------------------------------------------------- 1 | package uniq 2 | 3 | import ( 4 | "bufio" 5 | "bytes" 6 | "fmt" 7 | "github.com/guonaihong/flag" 8 | "io" 9 | "math" 10 | "os" 11 | ) 12 | 13 | var lineDelim byte = '\n' 14 | var endLineDelim byte = '\n' 15 | 16 | type ioCount struct { 17 | input int 18 | output int 19 | } 20 | 21 | type uniq struct { 22 | count map[string]ioCount 23 | } 24 | 25 | func (u *uniq) init() { 26 | u.count = map[string]ioCount{} 27 | } 28 | 29 | func die(format string, a ...interface{}) { 30 | fmt.Printf(format, a...) 31 | os.Exit(1) 32 | } 33 | 34 | func getSkipFields(skipFields int, l []byte) []byte { 35 | fields := bytes.Split(l, []byte(" ")) 36 | // Index starts from 1 37 | if skipFields >= 0 && skipFields < len(fields) { 38 | return bytes.Join(fields[skipFields:], []byte(" ")) 39 | } 40 | 41 | return []byte{} 42 | } 43 | 44 | func getSkipChars(skipChars int, l []byte) []byte { 45 | 46 | if skipChars >= 0 && skipChars < len(l) { 47 | return l[skipChars:] 48 | } 49 | 50 | return []byte{} 51 | } 52 | 53 | func getCheckChars(checkChars int, l []byte) []byte { 54 | if checkChars >= 0 && checkChars < len(l) { 55 | return l[:checkChars] 56 | } 57 | return l 58 | } 59 | 60 | func openInputFd(fileName string) (*os.File, error) { 61 | if fileName == "-" { 62 | return os.Stdin, nil 63 | } 64 | 65 | return os.Open(fileName) 66 | } 67 | 68 | func openOutputFd(fileName string) (*os.File, error) { 69 | if fileName == "-" { 70 | return os.Stdout, nil 71 | } 72 | 73 | return os.Create(fileName) 74 | } 75 | 76 | func closeInputFd(fd *os.File) { 77 | if fd != os.Stdin { 78 | fd.Close() 79 | } 80 | } 81 | 82 | func closeOutputFd(fd *os.File) { 83 | if fd != os.Stdout { 84 | fd.Close() 85 | } 86 | } 87 | 88 | func replaceEndLineDelim(l []byte) { 89 | if len(l) > 0 && l[len(l)-1] == '\n' && l[len(l)-1] != endLineDelim { 90 | l[len(l)-1] = endLineDelim 91 | } 92 | 93 | } 94 | 95 | func checkAllRepeated(arg string) { 96 | switch arg { 97 | case "none", "prepend", "separate": 98 | default: 99 | die("uniq: invalid argument `%s' for `--all-repeated' \nValid arguments are:\n - `none'\n - `prepend'\n - `separate'\n", arg) 100 | } 101 | } 102 | 103 | func checkGroup(arg string) { 104 | switch arg { 105 | case "prepend", "append", "separate", "both": 106 | default: 107 | die("uniq: invalid argument `%s' for `--group' \nValid arguments are:\n - `prepend'\n - `append'\n - `separate'\n - `separate'\n", arg) 108 | } 109 | } 110 | 111 | func writeLine(w io.Writer, l []byte) { 112 | replaceEndLineDelim(l) 113 | w.Write(l) 114 | } 115 | 116 | func Main(argv []string) { 117 | command := flag.NewFlagSet(argv[0], flag.ExitOnError) 118 | 119 | count := command.Opt("c, count", "prefix lines by the number of occurrences"). 120 | Flags(flag.PosixShort).NewBool(false) 121 | 122 | repeated := command.Opt("d, repeated", "only print duplicate lines"). 123 | Flags(flag.PosixShort).NewBool(false) 124 | 125 | dup := command.Opt("D", "print all duplicate lines"). 126 | Flags(flag.PosixShort).NewBool(false) 127 | 128 | allRepeated := command.Opt("all-repeated", "like -D, but allow separating groups with an empty line; \n"+ 129 | "METHOD={none(default),prepend,separate}"). 130 | NewString("") 131 | 132 | skipFields := command.Opt("f, skip-fields", "avoid comparing the first N fields"). 133 | NewInt(math.MinInt32) 134 | 135 | group := command.String("group", "", "show all items, separating groups with an empty line; \n"+ 136 | "METHOD={separate(default),prepend,append,both}") 137 | 138 | ignoreCase := command.Opt("i, ignore-case", "ignore differences in case when comparing"). 139 | Flags(flag.PosixShort).NewBool(false) 140 | 141 | skipChars := command.Opt("s, skip-chars", "avoid comparing the first N characters"). 142 | NewInt(math.MinInt32) 143 | 144 | unique := command.Opt("u, unique", "only print unique lines"). 145 | Flags(flag.PosixShort).NewBool(false) 146 | 147 | zeroTerminated := command.Opt("z, zero-terminated", "end lines with 0 byte, not newline"). 148 | Flags(flag.PosixShort).NewBool(false) 149 | 150 | checkChars := command.Opt("w, check-chars", "compare no more than N characters in lines"). 151 | NewInt(math.MinInt32) 152 | 153 | command.Parse(argv[1:]) 154 | 155 | args := command.Args() 156 | uniqHead := uniq{} 157 | uniqHead.init() 158 | 159 | if *dup && *allRepeated == "" { 160 | *allRepeated = "none" 161 | } 162 | 163 | if len(*group) > 0 { 164 | checkGroup(*group) 165 | if *count || *dup || *repeated || *unique { 166 | die("uniq: --group is mutually exclusive with -c/-d/-D/-u") 167 | } 168 | 169 | switch *group { 170 | case "prepend", "separate": 171 | *allRepeated = *group 172 | case "append": 173 | *allRepeated = "separate" 174 | case "both": 175 | *allRepeated = "separate" 176 | } 177 | } 178 | 179 | if len(*allRepeated) > 0 { 180 | checkAllRepeated(*allRepeated) 181 | } 182 | 183 | if *zeroTerminated { 184 | endLineDelim = '\000' 185 | } 186 | 187 | getKey := func(l []byte) string { 188 | if *ignoreCase { 189 | l = bytes.ToUpper(l) 190 | } 191 | 192 | if *skipFields != math.MinInt32 && *skipFields >= 0 { 193 | l = getSkipFields(*skipFields, l) 194 | //fmt.Printf("skip fields after l = (%s)\n", l) 195 | } 196 | 197 | if *skipChars != math.MinInt32 && *skipChars >= 0 { 198 | l = getSkipChars(*skipChars, l) 199 | } 200 | 201 | if *checkChars != math.MinInt32 && *checkChars >= 0 { 202 | l = getCheckChars(*checkChars, l) 203 | } 204 | 205 | //fmt.Printf("skipChars = %d, checkChars = %d\n", *skipChars, *checkChars) 206 | return string(l) 207 | } 208 | 209 | uniqCore := func(r io.Reader, w io.Writer) { 210 | br := bufio.NewReader(r) 211 | var allLine [][]byte 212 | var preLine []byte 213 | 214 | for lineNo := 0; ; lineNo++ { 215 | l, e := br.ReadBytes(lineDelim) 216 | if e != nil && len(l) == 0 { 217 | break 218 | } 219 | 220 | allLine = append(allLine, append([]byte{}, l...)) 221 | 222 | key := getKey(l) 223 | ioCount, _ := uniqHead.count[key] 224 | ioCount.input++ 225 | uniqHead.count[key] = ioCount 226 | } 227 | 228 | key := "" 229 | if len(allLine) > 0 && *group == "both" { 230 | w.Write([]byte{'\n'}) 231 | } 232 | 233 | for _, l := range allLine { 234 | 235 | key = getKey(l) 236 | 237 | ioCount, ok := uniqHead.count[key] 238 | if !ok { 239 | panic("not foud" + string(l)) 240 | } 241 | 242 | if *count { 243 | l = append([]byte(fmt.Sprintf("%6d ", ioCount.input)), l...) 244 | } 245 | 246 | if *unique { 247 | if ioCount.input == 1 { 248 | goto write 249 | } 250 | goto next 251 | } 252 | 253 | if *repeated { 254 | if ioCount.input > 1 { 255 | goto write 256 | } 257 | goto next 258 | } 259 | 260 | write: 261 | if len(*allRepeated) == 0 { 262 | if ioCount, ok = uniqHead.count[key]; ok && ioCount.output > 0 { 263 | continue 264 | } 265 | } else { 266 | if *group == "" && ioCount.input == 1 { 267 | continue 268 | } 269 | } 270 | 271 | if preLine != nil { 272 | writeLine(w, append(preLine, '\n')) 273 | preLine = nil 274 | } 275 | 276 | switch *allRepeated { 277 | case "none": 278 | case "prepend": 279 | if ioCount.output == 0 { 280 | l = append([]byte{'\n'}, l...) 281 | } 282 | case "separate": 283 | if ioCount.output == ioCount.input-1 { 284 | 285 | preLine = append([]byte{}, l...) 286 | goto next 287 | } 288 | 289 | } 290 | 291 | writeLine(w, l) 292 | next: 293 | ioCount, _ = uniqHead.count[key] 294 | ioCount.output++ 295 | uniqHead.count[key] = ioCount 296 | } 297 | 298 | if preLine != nil { 299 | if *group == "append" || *group == "both" { 300 | preLine = append(preLine, '\n') 301 | } 302 | writeLine(w, preLine) 303 | } 304 | } 305 | 306 | if len(args) == 0 { 307 | args = append(args, "-") 308 | } 309 | 310 | var r io.Reader 311 | var err error 312 | 313 | r, err = openInputFd(args[0]) 314 | if err != nil { 315 | die("uniq: %s\n", err) 316 | } 317 | 318 | w := os.Stdout 319 | if len(args) == 2 { 320 | w, err = openOutputFd(args[1]) 321 | if err != nil { 322 | die("uniq: %s\n", err) 323 | } 324 | } 325 | 326 | uniqCore(r, w) 327 | 328 | closeOutputFd(w) 329 | } 330 | -------------------------------------------------------------------------------- /uniq/uniq/uniq.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/guonaihong/coreutils/uniq" 5 | "os" 6 | ) 7 | 8 | func main() { 9 | uniq.Main(os.Args) 10 | } 11 | -------------------------------------------------------------------------------- /unlink/unlink.go: -------------------------------------------------------------------------------- 1 | package unlink 2 | 3 | import ( 4 | "fmt" 5 | "github.com/guonaihong/coreutils/utils" 6 | "github.com/guonaihong/flag" 7 | "syscall" 8 | ) 9 | 10 | func Main(argv []string) { 11 | command := flag.NewFlagSet(argv[0], flag.ExitOnError) 12 | 13 | command.Parse(argv[1:]) 14 | 15 | args := command.Args() 16 | 17 | if len(args) == 0 { 18 | utils.Die("unlink: missing operand\n") 19 | } 20 | 21 | err := syscall.Unlink(args[0]) 22 | if err != nil { 23 | fmt.Printf("unlink: cannot unlink '%s': %s\n", args[0], err) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /unlink/unlink/unlink.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/guonaihong/coreutils/unlink" 5 | "os" 6 | ) 7 | 8 | func main() { 9 | unlink.Main(os.Args) 10 | } 11 | -------------------------------------------------------------------------------- /utils/args.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | func NewArgs(args []string) []string { 4 | if len(args) == 0 { 5 | return append(args, "-") 6 | } 7 | return args 8 | } 9 | -------------------------------------------------------------------------------- /utils/parse_size.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | ) 7 | 8 | func IsDecimal(b byte) bool { 9 | return b >= '0' && b <= '9' 10 | } 11 | 12 | func IsDecimalStr(s string, max int) (i int, have bool) { 13 | return IsNeedStr(s, max, IsDecimal) 14 | } 15 | 16 | func IsOctal(b byte) bool { 17 | if b >= '0' && b <= '7' { 18 | return true 19 | } 20 | 21 | return false 22 | } 23 | 24 | func IsOctalStr(s string, max int) (i int, haveOctal bool) { 25 | return IsNeedStr(s, max, IsOctal) 26 | } 27 | 28 | func IsXdigit(b byte) bool { 29 | if b >= '0' && b <= '9' || 30 | b >= 'a' && b <= 'f' || 31 | b >= 'A' && b <= 'F' { 32 | return true 33 | } 34 | return false 35 | } 36 | 37 | func IsXdigitStr(s string, max int) (i int, haveHex bool) { 38 | return IsNeedStr(s, max, IsXdigit) 39 | } 40 | 41 | func IsNeedStr(s string, max int, is func(b byte) bool) (i int, have bool) { 42 | for i = 0; i < len(s); i++ { 43 | if i >= max { 44 | return i, have 45 | } 46 | 47 | if !is(s[i]) { 48 | return i, have 49 | } 50 | 51 | have = true 52 | } 53 | 54 | return i, have 55 | } 56 | 57 | type Size int 58 | 59 | const ( 60 | Byte Size = 1 61 | B = 512 62 | K = 1024 * Byte 63 | M = 1024 * K 64 | G = 1024 * M 65 | T = 1024 * G 66 | P = 1024 * T 67 | E = 1024 * P 68 | //Z = 1024 * E 69 | //Y = 1024 * Z 70 | ) 71 | 72 | const ( 73 | KB = 1000 74 | MB = 1000 * KB 75 | GB = 1000 * MB 76 | TB = 1000 * GB 77 | PB = 1000 * TB 78 | EB = 1000 * PB 79 | //ZB = 1000 * EB 80 | //YB = 1000 * ZB 81 | ) 82 | 83 | func (s Size) IntPtr() *int { 84 | n := int(s) 85 | return &n 86 | } 87 | 88 | func HeadParseSize(s string) (Size, error) { 89 | sign := '+' 90 | if len(s) > 0 && (s[0] == '-' || s[0] == '+') { 91 | sign = rune(s[0]) 92 | s = s[1:] 93 | } 94 | 95 | i, have := IsDecimalStr(s, len(s)) 96 | if !have { 97 | return Size(0), fmt.Errorf("invalid number of bytes: '%s'", s) 98 | } 99 | 100 | n := 0 101 | suffix := Size(1) 102 | 103 | fmt.Sscanf(s, "%d", &n) 104 | 105 | if i >= len(s) { 106 | goto quit 107 | } 108 | 109 | switch s[i:] { 110 | case "B": 111 | suffix = B 112 | case "kB": 113 | suffix = KB 114 | case "MB": 115 | suffix = MB 116 | case "GB": 117 | suffix = GB 118 | case "TB": 119 | suffix = TB 120 | case "PB": 121 | suffix = PB 122 | case "EB": 123 | suffix = EB 124 | /* 125 | case "ZB": 126 | suffix = ZB 127 | case "YB": 128 | suffix = YB 129 | */ 130 | case "K": 131 | suffix = K 132 | case "M": 133 | suffix = M 134 | case "G": 135 | suffix = G 136 | case "T": 137 | suffix = T 138 | case "P": 139 | suffix = P 140 | case "E": 141 | suffix = E 142 | /* 143 | case "Z": 144 | suffix = Z 145 | case "Y": 146 | suffix = Y 147 | */ 148 | default: 149 | return 0, errors.New("Unsupported suffix") 150 | } 151 | 152 | quit: 153 | if sign == '-' { 154 | return Size(-n * int(suffix)), nil 155 | } 156 | 157 | return Size(n * int(suffix)), nil 158 | } 159 | -------------------------------------------------------------------------------- /utils/parse_size_test.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestHeadParseSize(t *testing.T) { 8 | n, _ := HeadParseSize("1") 9 | if n != Size(1) { 10 | t.Errorf("Value is not equal to -1\n") 11 | } 12 | 13 | n, err := HeadParseSize("1kB") 14 | if n != KB || err != nil { 15 | t.Errorf("Value is not equal to %d:%d:err(%v)\n", int(KB), n, err) 16 | } 17 | 18 | n, err = HeadParseSize("1MB") 19 | if n != MB || err != nil { 20 | t.Errorf("Value is not equal to %d:%d:err(%v)\n", int(MB), n, err) 21 | } 22 | 23 | n, err = HeadParseSize("1K") 24 | if n != K || err != nil { 25 | t.Errorf("Value is not equal to %d:%d:err(%v)\n", int(K), n, err) 26 | } 27 | 28 | n, err = HeadParseSize("123MB") 29 | if n != 123*1000*1000 || err != nil { 30 | t.Errorf("Value is not equal to %d:%d:err(%v)\n", int(123*MB), n, err) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /utils/type.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | func String(s string) *string { 4 | return &s 5 | } 6 | 7 | func Int64(i64 int64) *int64 { 8 | return &i64 9 | } 10 | 11 | func Int(i int) *int { 12 | return &i 13 | } 14 | 15 | func Bool(b bool) *bool { 16 | return &b 17 | } 18 | -------------------------------------------------------------------------------- /utils/utils.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "bytes" 5 | "encoding/binary" 6 | "fmt" 7 | "math" 8 | "math/rand" 9 | "os" 10 | "time" 11 | ) 12 | 13 | func Die(format string, a ...interface{}) { 14 | fmt.Printf(format, a...) 15 | os.Exit(1) 16 | } 17 | 18 | type File struct { 19 | *os.File 20 | } 21 | 22 | func (f *File) Close() { 23 | if f.File == os.Stdin { 24 | return 25 | } 26 | 27 | f.File.Close() 28 | } 29 | 30 | func OpenFile(fileName string) (*File, error) { 31 | if fileName == "-" { 32 | return &File{File: os.Stdin}, nil 33 | } 34 | 35 | fd, err := os.Open(fileName) 36 | if err != nil { 37 | return nil, err 38 | } 39 | 40 | return &File{File: fd}, nil 41 | } 42 | 43 | func OpenOutputFd(fileName string) (*os.File, error) { 44 | if fileName == "-" { 45 | return os.Stdout, nil 46 | } 47 | 48 | return os.Create(fileName) 49 | } 50 | 51 | func CloseOutputFd(fd *os.File) { 52 | if fd != os.Stdout { 53 | fd.Close() 54 | } 55 | } 56 | 57 | func readFile(fileName string, body []byte) (n int, err error) { 58 | 59 | var fd *os.File 60 | 61 | fd, err = os.Open(fileName) 62 | if err != nil { 63 | return 0, err 64 | } 65 | 66 | r := rand.New(rand.NewSource(time.Now().UnixNano())) 67 | 68 | n1, err := fd.Seek(int64(0), 2) 69 | if err != nil { 70 | return 0, err 71 | } 72 | 73 | if n1 <= 0 { 74 | n1 = math.MaxInt32 75 | } 76 | 77 | fd.Seek(r.Int63n(n1), os.SEEK_SET) 78 | return fd.Read(body) 79 | } 80 | 81 | func GetRandSource(fileName string) (*rand.Rand, error) { 82 | 83 | seed := int64(0) 84 | buf := make([]byte, 8) 85 | 86 | _, err := readFile(fileName, buf) 87 | if err != nil { 88 | return nil, err 89 | } 90 | 91 | read := bytes.NewReader(buf) 92 | 93 | err = binary.Read(read, binary.LittleEndian, &seed) 94 | if err != nil { 95 | return nil, err 96 | } 97 | 98 | return rand.New(rand.NewSource(seed)), nil 99 | } 100 | -------------------------------------------------------------------------------- /utils/walk.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "errors" 5 | "os" 6 | "path/filepath" 7 | "sort" 8 | ) 9 | 10 | //Copy code from the golang standard library 11 | 12 | // SkipDir is used as a return value from WalkFuncs to indicate that 13 | // the directory named in the call is to be skipped. It is not returned 14 | // as an error by any function. 15 | var SkipDir = errors.New("skip this directory") 16 | 17 | // WalkFunc is the type of the function called for each file or directory 18 | // visited by Walk. The path argument contains the argument to Walk as a 19 | // prefix; that is, if Walk is called with "dir", which is a directory 20 | // containing the file "a", the walk function will be called with argument 21 | // "dir/a". The info argument is the os.FileInfo for the named path. 22 | // 23 | // If there was a problem walking to the file or directory named by path, the 24 | // incoming error will describe the problem and the function can decide how 25 | // to handle that error (and Walk will not descend into that directory). In the 26 | // case of an error, the info argument will be nil. If an error is returned, 27 | // processing stops. The sole exception is when the function returns the special 28 | // value SkipDir. If the function returns SkipDir when invoked on a directory, 29 | // Walk skips the directory's contents entirely. If the function returns SkipDir 30 | // when invoked on a non-directory file, Walk skips the remaining files in the 31 | // containing directory. 32 | type WalkFunc func(path string, info os.FileInfo, err error) error 33 | 34 | type Walk struct { 35 | stat func(name string) (os.FileInfo, error) 36 | onlyOne bool 37 | } 38 | 39 | func NewWalk(onlyOne bool, stat func(name string) (os.FileInfo, error)) *Walk { 40 | if stat == nil { 41 | stat = os.Lstat 42 | } 43 | 44 | return &Walk{stat: stat, onlyOne: onlyOne} 45 | } 46 | 47 | // walk recursively descends path, calling walkFn. 48 | func (w *Walk) walk(path string, info os.FileInfo, walkFn WalkFunc) error { 49 | if !info.IsDir() { 50 | return walkFn(path, info, nil) 51 | } 52 | 53 | names, err := readDirNames(path) 54 | err1 := walkFn(path, info, err) 55 | // If err != nil, walk can't walk into this directory. 56 | // err1 != nil means walkFn want walk to skip this directory or stop walking. 57 | // Therefore, if one of err and err1 isn't nil, walk will return. 58 | if err != nil || err1 != nil { 59 | // The caller's behavior is controlled by the return value, which is decided 60 | // by walkFn. walkFn may ignore err and return nil. 61 | // If walkFn returns SkipDir, it will be handled by the caller. 62 | // So walk should return whatever walkFn returns. 63 | return err1 64 | } 65 | 66 | for _, name := range names { 67 | filename := filepath.Join(path, name) 68 | fileInfo, err := w.stat(filename) 69 | if err != nil { 70 | if err := walkFn(filename, fileInfo, err); err != nil && err != SkipDir { 71 | return err 72 | } 73 | } else { 74 | err = w.walk(filename, fileInfo, walkFn) 75 | if err != nil { 76 | if !fileInfo.IsDir() || err != SkipDir { 77 | return err 78 | } 79 | } 80 | } 81 | } 82 | return nil 83 | } 84 | 85 | // Walk walks the file tree rooted at root, calling walkFn for each file or 86 | // directory in the tree, including root. All errors that arise visiting files 87 | // and directories are filtered by walkFn. The files are walked in lexical 88 | // order, which makes the output deterministic but means that for very 89 | // large directories Walk can be inefficient. 90 | // Walk does not follow symbolic links. 91 | func (w *Walk) Walk(root string, walkFn WalkFunc) error { 92 | info, err := w.stat(root) 93 | if err != nil { 94 | err = walkFn(root, nil, err) 95 | } else { 96 | if w.onlyOne { 97 | w.stat = os.Lstat 98 | } 99 | err = w.walk(root, info, walkFn) 100 | } 101 | if err == SkipDir { 102 | return nil 103 | } 104 | return err 105 | } 106 | 107 | // readDirNames reads the directory named by dirname and returns 108 | // a sorted list of directory entries. 109 | func readDirNames(dirname string) ([]string, error) { 110 | f, err := os.Open(dirname) 111 | if err != nil { 112 | return nil, err 113 | } 114 | names, err := f.Readdirnames(-1) 115 | f.Close() 116 | if err != nil { 117 | return nil, err 118 | } 119 | sort.Strings(names) 120 | return names, nil 121 | } 122 | -------------------------------------------------------------------------------- /wc/wc.go: -------------------------------------------------------------------------------- 1 | package wc 2 | 3 | import ( 4 | "bufio" 5 | "bytes" 6 | "fmt" 7 | "github.com/guonaihong/coreutils/utils" 8 | "github.com/guonaihong/flag" 9 | "os" 10 | ) 11 | 12 | type wcCmd struct { 13 | bytesCounts *bool 14 | characterCounts *bool 15 | lines *bool 16 | files0From *string 17 | maxLineLength *bool 18 | words *bool 19 | } 20 | 21 | func (w *wcCmd) main(fd *os.File) { 22 | br := bufio.NewReader(fd) 23 | 24 | totalBytes, totalLine, maxLineLength := 0, 0, 0 25 | totalWords := 0 26 | 27 | for { 28 | l, e := br.ReadBytes('\n') 29 | if e != nil && len(l) == 0 { 30 | break 31 | } 32 | 33 | ls := bytes.Split(l, []byte{' '}) 34 | for _, v := range ls { 35 | if len(bytes.TrimSpace(v)) == 0 { 36 | continue 37 | } 38 | totalWords++ 39 | } 40 | 41 | totalBytes += len(l) 42 | totalLine++ 43 | if len(l) > maxLineLength { 44 | maxLineLength = len(l) 45 | } 46 | } 47 | 48 | fmt.Printf("%4d %4d %4d %s\n", totalLine, totalWords, totalBytes, fd.Name()) 49 | 50 | } 51 | 52 | func Main(argv []string) { 53 | command := flag.NewFlagSet(argv[0], flag.ContinueOnError) 54 | 55 | wc := wcCmd{} 56 | 57 | wc.bytesCounts = command.Opt("c, bytes", "print the byte counts"). 58 | Flags(flag.PosixShort).NewBool(false) 59 | 60 | wc.characterCounts = command.Opt("m, chars", "print the character counts"). 61 | Flags(flag.PosixShort).NewBool(false) 62 | 63 | wc.lines = command.Opt("l, lines", "print the newline counts"). 64 | Flags(flag.PosixShort).NewBool(false) 65 | 66 | wc.files0From = command.Opt("files0-from", "read input from the files specified by\n"+ 67 | "NUL-terminated names in file F;\n"+ 68 | "If F is - then read names from standard input"). 69 | Flags(flag.PosixShort).NewString("") 70 | 71 | wc.maxLineLength = command.Opt("L, max-line-length", "print the maximum display width"). 72 | Flags(flag.PosixShort).NewBool(false) 73 | 74 | wc.words = command.Opt("w, words", "print the word counts"). 75 | Flags(flag.PosixShort).NewBool(false) 76 | 77 | command.Parse(argv[1:]) 78 | 79 | args := command.Args() 80 | for _, v := range args { 81 | fd, err := utils.OpenInputFd(v) 82 | if err != nil { 83 | utils.Die("wc: %s\n", err) 84 | } 85 | wc.main(fd) 86 | utils.CloseInputFd(fd) 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /wc/wc/wc.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/guonaihong/coreutils/wc" 5 | "os" 6 | ) 7 | 8 | func main() { 9 | wc.Main(os.Args) 10 | } 11 | -------------------------------------------------------------------------------- /whoami/README.md: -------------------------------------------------------------------------------- 1 | # whoami 2 | 3 | #### 简介 4 | whoami 5 | 6 | #### install 7 | ``` 8 | env GOPATH=`pwd` go get -u github.com/guonaihong/coreutils/whoami/whoami 9 | ``` 10 | -------------------------------------------------------------------------------- /whoami/whoami.go: -------------------------------------------------------------------------------- 1 | package whoami 2 | 3 | import ( 4 | "fmt" 5 | "os/user" 6 | ) 7 | 8 | func Main(argv []string) { 9 | user, err := user.Current() 10 | if err != nil { 11 | panic(err) 12 | } 13 | fmt.Printf("%s\n", user.Name) 14 | } 15 | -------------------------------------------------------------------------------- /whoami/whoami/whomai.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/guonaihong/coreutils/whoami" 5 | "os" 6 | ) 7 | 8 | func main() { 9 | whoami.Main(os.Args) 10 | } 11 | -------------------------------------------------------------------------------- /yes/README.md: -------------------------------------------------------------------------------- 1 | # yes 2 | 3 | #### 简介 4 | yes 5 | 6 | #### install 7 | ``` 8 | env GOPATH=`pwd` go get -u github.com/guonaihong/coreutils/yes/yes 9 | ``` 10 | -------------------------------------------------------------------------------- /yes/yes.go: -------------------------------------------------------------------------------- 1 | package yes 2 | 3 | import ( 4 | "fmt" 5 | "github.com/guonaihong/flag" 6 | "os" 7 | "strings" 8 | ) 9 | 10 | func Main(argv []string) { 11 | command := flag.NewFlagSet(argv[0], flag.ExitOnError) 12 | v := command.Bool("version", false, "output version information and exit") 13 | if *v { 14 | fmt.Printf("todo output version\n") 15 | os.Exit(0) 16 | } 17 | 18 | command.Parse(argv[1:]) 19 | args := command.Args() 20 | 21 | output := "y\n" 22 | 23 | if len(args) > 0 { 24 | output = strings.Join(args, " ") 25 | output += "\n" 26 | } 27 | 28 | for { 29 | os.Stdout.Write([]byte(output)) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /yes/yes/yes.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/guonaihong/coreutils/yes" 5 | "os" 6 | ) 7 | 8 | func main() { 9 | yes.Main(os.Args) 10 | } 11 | --------------------------------------------------------------------------------