github.com/zhongdalu/gf@v1.0.0/g/os/gfile/gfile.go (about) 1 // Copyright 2017 gf Author(https://github.com/zhongdalu/gf). All Rights Reserved. 2 // 3 // This Source Code Form is subject to the terms of the MIT License. 4 // If a copy of the MIT was not distributed with this file, 5 // You can obtain one at https://github.com/zhongdalu/gf. 6 7 // Package gfile provides easy-to-use operations for file system. 8 package gfile 9 10 import ( 11 "bytes" 12 "errors" 13 "fmt" 14 "io" 15 "io/ioutil" 16 "os" 17 "os/exec" 18 "os/user" 19 "path/filepath" 20 "runtime" 21 "sort" 22 "strings" 23 "time" 24 25 "github.com/zhongdalu/gf/g/container/gtype" 26 "github.com/zhongdalu/gf/g/text/gregex" 27 "github.com/zhongdalu/gf/g/text/gstr" 28 "github.com/zhongdalu/gf/g/util/gconv" 29 ) 30 31 const ( 32 // Separator for file system. 33 Separator = string(filepath.Separator) 34 // Default perm for file opening. 35 gDEFAULT_PERM = 0666 36 ) 37 38 var ( 39 // The absolute file path for main package. 40 // It can be only checked and set once. 41 mainPkgPath = gtype.NewString() 42 ) 43 44 // Mkdir creates directories recursively with given <path>. 45 // The parameter <path> is suggested to be absolute path. 46 func Mkdir(path string) error { 47 err := os.MkdirAll(path, os.ModePerm) 48 if err != nil { 49 return err 50 } 51 return nil 52 } 53 54 // Create creates file with given <path> recursively. 55 // The parameter <path> is suggested to be absolute path. 56 func Create(path string) (*os.File, error) { 57 dir := Dir(path) 58 if !Exists(dir) { 59 Mkdir(dir) 60 } 61 return os.Create(path) 62 } 63 64 // Open opens file/directory readonly. 65 func Open(path string) (*os.File, error) { 66 return os.Open(path) 67 } 68 69 // OpenFile opens file/directory with given <flag> and <perm>. 70 func OpenFile(path string, flag int, perm os.FileMode) (*os.File, error) { 71 return os.OpenFile(path, flag, perm) 72 } 73 74 // OpenWithFlag opens file/directory with default perm and given <flag>. 75 func OpenWithFlag(path string, flag int) (*os.File, error) { 76 f, err := os.OpenFile(path, flag, gDEFAULT_PERM) 77 if err != nil { 78 return nil, err 79 } 80 return f, nil 81 } 82 83 // OpenWithFlagPerm opens file/directory with given <flag> and <perm>. 84 func OpenWithFlagPerm(path string, flag int, perm int) (*os.File, error) { 85 f, err := os.OpenFile(path, flag, os.FileMode(perm)) 86 if err != nil { 87 return nil, err 88 } 89 return f, nil 90 } 91 92 // Exists checks whether given <path> exist. 93 func Exists(path string) bool { 94 if _, err := os.Stat(path); !os.IsNotExist(err) { 95 return true 96 } 97 return false 98 } 99 100 // IsDir checks whether given <path> a directory. 101 func IsDir(path string) bool { 102 s, err := os.Stat(path) 103 if err != nil { 104 return false 105 } 106 return s.IsDir() 107 } 108 109 // Pwd returns absolute path of current working directory. 110 func Pwd() string { 111 path, _ := os.Getwd() 112 return path 113 } 114 115 // IsFile checks whether given <path> a file, which means it's not a directory. 116 func IsFile(path string) bool { 117 s, err := os.Stat(path) 118 if err != nil { 119 return false 120 } 121 return !s.IsDir() 122 } 123 124 // Alias of Stat. 125 // See Stat. 126 func Info(path string) (os.FileInfo, error) { 127 return Stat(path) 128 } 129 130 // Stat returns a FileInfo describing the named file. 131 // If there is an error, it will be of type *PathError. 132 func Stat(path string) (os.FileInfo, error) { 133 return os.Stat(path) 134 } 135 136 // Move renames (moves) <src> to <dst> path. 137 func Move(src string, dst string) error { 138 return os.Rename(src, dst) 139 } 140 141 // Alias of Move. 142 // See Move. 143 func Rename(src string, dst string) error { 144 return Move(src, dst) 145 } 146 147 // Copy file/directory from <src> to <dst>. 148 // 149 // If <src> is file, it calls CopyFile to implements copy feature, 150 // or else it calls CopyDir. 151 func Copy(src string, dst string) error { 152 if IsFile(src) { 153 return CopyFile(src, dst) 154 } 155 return CopyDir(src, dst) 156 } 157 158 // CopyFile copies the contents of the file named src to the file named 159 // by dst. The file will be created if it does not already exist. If the 160 // destination file exists, all it's contents will be replaced by the contents 161 // of the source file. The file mode will be copied from the source and 162 // the copied data is synced/flushed to stable storage. 163 // Thanks: https://gist.github.com/r0l1/92462b38df26839a3ca324697c8cba04 164 func CopyFile(src, dst string) (err error) { 165 in, err := os.Open(src) 166 if err != nil { 167 return 168 } 169 defer func() { 170 if e := in.Close(); e != nil { 171 err = e 172 } 173 }() 174 out, err := os.Create(dst) 175 if err != nil { 176 return 177 } 178 defer func() { 179 if e := out.Close(); e != nil { 180 err = e 181 } 182 }() 183 _, err = io.Copy(out, in) 184 if err != nil { 185 return 186 } 187 err = out.Sync() 188 if err != nil { 189 return 190 } 191 si, err := os.Stat(src) 192 if err != nil { 193 return 194 } 195 err = os.Chmod(dst, si.Mode()) 196 if err != nil { 197 return 198 } 199 return 200 } 201 202 // CopyDir recursively copies a directory tree, attempting to preserve permissions. 203 // Source directory must exist, destination directory must *not* exist. 204 // Symlinks are ignored and skipped. 205 func CopyDir(src string, dst string) (err error) { 206 src = filepath.Clean(src) 207 dst = filepath.Clean(dst) 208 si, err := os.Stat(src) 209 if err != nil { 210 return err 211 } 212 if !si.IsDir() { 213 return fmt.Errorf("source is not a directory") 214 } 215 _, err = os.Stat(dst) 216 if err != nil && !os.IsNotExist(err) { 217 return 218 } 219 if err == nil { 220 return fmt.Errorf("destination already exists") 221 } 222 err = os.MkdirAll(dst, si.Mode()) 223 if err != nil { 224 return 225 } 226 entries, err := ioutil.ReadDir(src) 227 if err != nil { 228 return 229 } 230 for _, entry := range entries { 231 srcPath := filepath.Join(src, entry.Name()) 232 dstPath := filepath.Join(dst, entry.Name()) 233 if entry.IsDir() { 234 err = CopyDir(srcPath, dstPath) 235 if err != nil { 236 return 237 } 238 } else { 239 // Skip symlinks. 240 if entry.Mode()&os.ModeSymlink != 0 { 241 continue 242 } 243 err = CopyFile(srcPath, dstPath) 244 if err != nil { 245 return 246 } 247 } 248 } 249 return 250 } 251 252 // DirNames returns sub-file names of given directory <path>. 253 func DirNames(path string) ([]string, error) { 254 f, err := os.Open(path) 255 if err != nil { 256 return nil, err 257 } 258 list, err := f.Readdirnames(-1) 259 f.Close() 260 if err != nil { 261 return nil, err 262 } 263 return list, nil 264 } 265 266 // Glob returns the names of all files matching pattern or nil 267 // if there is no matching file. The syntax of patterns is the same 268 // as in Match. The pattern may describe hierarchical names such as 269 // /usr/*/bin/ed (assuming the Separator is '/'). 270 // 271 // Glob ignores file system errors such as I/O errors reading directories. 272 // The only possible returned error is ErrBadPattern, when pattern 273 // is malformed. 274 func Glob(pattern string, onlyNames ...bool) ([]string, error) { 275 if list, err := filepath.Glob(pattern); err == nil { 276 if len(onlyNames) > 0 && onlyNames[0] && len(list) > 0 { 277 array := make([]string, len(list)) 278 for k, v := range list { 279 array[k] = Basename(v) 280 } 281 return array, nil 282 } 283 return list, nil 284 } else { 285 return nil, err 286 } 287 } 288 289 // Remove deletes all file/directory with <path> parameter. 290 // If parameter <path> is directory, it deletes it recursively. 291 func Remove(path string) error { 292 return os.RemoveAll(path) 293 } 294 295 // IsReadable checks whether given <path> is readable. 296 func IsReadable(path string) bool { 297 result := true 298 file, err := os.OpenFile(path, os.O_RDONLY, gDEFAULT_PERM) 299 if err != nil { 300 result = false 301 } 302 file.Close() 303 return result 304 } 305 306 // IsWritable checks whether given <path> is writable. 307 // 308 // @TODO improve performance; use golang.org/x/sys to cross-plat-form 309 func IsWritable(path string) bool { 310 result := true 311 if IsDir(path) { 312 // If it's a directory, create a temporary file to test whether it's writable. 313 tmpFile := strings.TrimRight(path, Separator) + Separator + gconv.String(time.Now().UnixNano()) 314 if f, err := Create(tmpFile); err != nil || !Exists(tmpFile) { 315 result = false 316 } else { 317 f.Close() 318 Remove(tmpFile) 319 } 320 } else { 321 // 如果是文件,那么判断文件是否可打开 322 file, err := os.OpenFile(path, os.O_WRONLY, gDEFAULT_PERM) 323 if err != nil { 324 result = false 325 } 326 file.Close() 327 } 328 return result 329 } 330 331 // See os.Chmod. 332 func Chmod(path string, mode os.FileMode) error { 333 return os.Chmod(path, mode) 334 } 335 336 // ScanDir returns all sub-files with absolute paths of given <path>, 337 // It scans directory recursively if given parameter <recursive> is true. 338 func ScanDir(path string, pattern string, recursive ...bool) ([]string, error) { 339 list, err := doScanDir(path, pattern, recursive...) 340 if err != nil { 341 return nil, err 342 } 343 if len(list) > 0 { 344 sort.Strings(list) 345 } 346 return list, nil 347 } 348 349 // doScanDir is an internal method which scans directory 350 // and returns the absolute path list of files that are not sorted. 351 // 352 // The pattern parameter <pattern> supports multiple file name patterns, 353 // using the ',' symbol to separate multiple patterns. 354 // 355 // It scans directory recursively if given parameter <recursive> is true. 356 func doScanDir(path string, pattern string, recursive ...bool) ([]string, error) { 357 list := ([]string)(nil) 358 file, err := os.Open(path) 359 if err != nil { 360 return nil, err 361 } 362 defer file.Close() 363 names, err := file.Readdirnames(-1) 364 if err != nil { 365 return nil, err 366 } 367 for _, name := range names { 368 path := fmt.Sprintf("%s%s%s", path, Separator, name) 369 if IsDir(path) && len(recursive) > 0 && recursive[0] { 370 array, _ := doScanDir(path, pattern, true) 371 if len(array) > 0 { 372 list = append(list, array...) 373 } 374 } 375 // If it meets pattern, then add it to the result list. 376 for _, p := range strings.Split(pattern, ",") { 377 if match, err := filepath.Match(strings.TrimSpace(p), name); err == nil && match { 378 list = append(list, path) 379 } 380 } 381 } 382 return list, nil 383 } 384 385 // RealPath converts the given <path> to its absolute path 386 // and checks if the file path exists. 387 // If the file does not exist, return an empty string. 388 func RealPath(path string) string { 389 p, err := filepath.Abs(path) 390 if err != nil { 391 return "" 392 } 393 if !Exists(p) { 394 return "" 395 } 396 return p 397 } 398 399 // SelfPath returns absolute file path of current running process(binary). 400 func SelfPath() string { 401 p, _ := filepath.Abs(os.Args[0]) 402 return p 403 } 404 405 // SelfName returns file name of current running process(binary). 406 func SelfName() string { 407 return Basename(SelfPath()) 408 } 409 410 // SelfDir returns absolute directory path of current running process(binary). 411 func SelfDir() string { 412 return filepath.Dir(SelfPath()) 413 } 414 415 // Basename returns the last element of path. 416 // Trailing path separators are removed before extracting the last element. 417 // If the path is empty, Base returns ".". 418 // If the path consists entirely of separators, Base returns a single separator. 419 func Basename(path string) string { 420 return filepath.Base(path) 421 } 422 423 // Dir returns all but the last element of path, typically the path's directory. 424 // After dropping the final element, Dir calls Clean on the path and trailing 425 // slashes are removed. 426 // If the path is empty, Dir returns ".". 427 // If the path consists entirely of separators, Dir returns a single separator. 428 // The returned path does not end in a separator unless it is the root directory. 429 func Dir(path string) string { 430 return filepath.Dir(path) 431 } 432 433 // Ext returns the file name extension used by path. 434 // The extension is the suffix beginning at the final dot 435 // in the final element of path; it is empty if there is 436 // no dot. 437 // 438 // Note: the result contains symbol '.'. 439 func Ext(path string) string { 440 return filepath.Ext(path) 441 } 442 443 // Home returns absolute path of current user's home directory. 444 func Home() (string, error) { 445 u, err := user.Current() 446 if nil == err { 447 return u.HomeDir, nil 448 } 449 if "windows" == runtime.GOOS { 450 return homeWindows() 451 } 452 return homeUnix() 453 } 454 455 func homeUnix() (string, error) { 456 if home := os.Getenv("HOME"); home != "" { 457 return home, nil 458 } 459 var stdout bytes.Buffer 460 cmd := exec.Command("sh", "-c", "eval echo ~$USER") 461 cmd.Stdout = &stdout 462 if err := cmd.Run(); err != nil { 463 return "", err 464 } 465 466 result := strings.TrimSpace(stdout.String()) 467 if result == "" { 468 return "", errors.New("blank output when reading home directory") 469 } 470 471 return result, nil 472 } 473 474 func homeWindows() (string, error) { 475 drive := os.Getenv("HOMEDRIVE") 476 path := os.Getenv("HOMEPATH") 477 home := drive + path 478 if drive == "" || path == "" { 479 home = os.Getenv("USERPROFILE") 480 } 481 if home == "" { 482 return "", errors.New("HOMEDRIVE, HOMEPATH, and USERPROFILE are blank") 483 } 484 485 return home, nil 486 } 487 488 // MainPkgPath returns absolute file path of package main, 489 // which contains the entrance function main. 490 // 491 // It's only available in develop environment. 492 // 493 // Note1: Only valid for source development environments, 494 // IE only valid for systems that generate this executable. 495 // Note2: When the method is called for the first time, if it is in an asynchronous goroutine, 496 // the method may not get the main package path. 497 func MainPkgPath() string { 498 path := mainPkgPath.Val() 499 if path != "" { 500 if path == "-" { 501 return "" 502 } 503 return path 504 } 505 for i := 1; i < 10000; i++ { 506 if _, file, _, ok := runtime.Caller(i); ok { 507 // <file> is separated by '/' 508 if gstr.Contains(file, "/gf/g/") { 509 continue 510 } 511 if Ext(file) != ".go" { 512 continue 513 } 514 // separator of <file> '/' will be converted to Separator. 515 for path = Dir(file); len(path) > 1 && Exists(path) && path[len(path)-1] != os.PathSeparator; { 516 files, _ := ScanDir(path, "*.go") 517 for _, v := range files { 518 if gregex.IsMatchString(`package\s+main`, GetContents(v)) { 519 mainPkgPath.Set(path) 520 return path 521 } 522 } 523 path = Dir(path) 524 } 525 526 } else { 527 break 528 } 529 } 530 // If it fails finding the path, then mark it as "-", 531 // which means it will never do this search again. 532 mainPkgPath.Set("-") 533 return "" 534 } 535 536 // See os.TempDir(). 537 func TempDir() string { 538 return os.TempDir() 539 }