package search import ( "b612.me/stario" "b612.me/starlog" "b612.me/startext" "bufio" "errors" "fmt" "github.com/spf13/cobra" "io/ioutil" "os" "path/filepath" "regexp" "strconv" "strings" ) var stFolder string var stNum, stMax, stMin int var stautoGBK, allowEmoji, onlyShowFileName bool func init() { Cmd.Flags().StringVarP(&stFolder, "folder", "f", "./", "搜索的文件夹") Cmd.Flags().IntVarP(&stNum, "thread-num", "n", 5, "并发搜寻协程数") Cmd.Flags().BoolVarP(&allowEmoji, "allow-emoji", "e", false, "使用\\U输入Emoji") Cmd.Flags().BoolVarP(&stautoGBK, "autogbk", "g", false, "自动GBK识别") Cmd.Flags().BoolVarP(&onlyShowFileName, "only-show-filename", "o", false, "只显示文件名") Cmd.Flags().IntVar(&stMax, "max", 0, "行最大字数") Cmd.Flags().IntVar(&stMin, "min", 0, "行最小字数") } var Cmd = &cobra.Command{ Use: "st", Short: "搜索文件中特定字符串", Long: "搜索文件中特定字符串", Run: func(this *cobra.Command, args []string) { if len(args) != 2 { starlog.Errorln("应当传入两个参数,搜寻文件后缀和搜寻文本") os.Exit(1) } if allowEmoji { args[1], _ = replaceUnicodeEmoji(args[1]) } err := searchText(stFolder, args[0], args[1], stNum, stautoGBK, stMax, stMin, onlyShowFileName) if err != nil { os.Exit(2) } return }, } func replaceUnicodeEmoji(text string) (string, error) { // 查找含有 \U 开头的代码点 re := regexp.MustCompile(`\\U([0-9A-Fa-f]{1,8})`) matches := re.FindAllStringSubmatch(text, -1) // 如果没有匹配到任何内容,则直接返回原始文本 if matches == nil { return text, nil } // 将代码点替换为相应的表情符号 for _, match := range matches { emoji, err := unicodeToEmoji(match[1]) if err != nil { return "", err } text = strings.Replace(text, match[0], emoji, -1) } return text, nil } func unicodeToEmoji(codepoint string) (string, error) { // 将16进制字符串转换为 uint32 类型 hexcode, err := strconv.ParseUint(codepoint, 16, 32) if err != nil { return "", err } // 检查代码点是否位于Unicode BMP(基本多文本平面)中 if hexcode > 0x10FFFF { return "", errors.New("invalid Unicode code point") } // 将 uint32 类型的代码点转换为 rune 类型 r := rune(hexcode) // 将 rune 格式化为 8 位宽度的 16 进制数值并前置 0 填充 emoji := fmt.Sprintf("%08X", hexcode) // 将 rune 类型的字符转换为对应的字符串表情符号 emoji = string(r) return emoji, nil } func searchText(folder string, filematch string, text string, thread int, autoGBK bool, max, min int, onlyShowFileName bool) error { data, err := ioutil.ReadDir(folder) if err != nil { starlog.Errorln("read folder failed", folder, err) return err } wg := stario.NewWaitGroup(thread) searchFn := func(filepath string, text string) { //starlog.Debugln("searching", filepath, text) defer wg.Done() fp, err := os.Open(filepath) if err != nil { starlog.Errorln("open file failed", filepath, err) return } defer fp.Close() reader := bufio.NewReader(fp) count := 0 for { origin, err := reader.ReadString('\n') count++ if stautoGBK && startext.IsGBK([]byte(origin)) { originByte, _ := startext.GBK2UTF8([]byte(origin)) origin = string(originByte) } origin = strings.TrimSpace(origin) if max != 0 && len(origin) > max { continue } if min != 0 && len(origin) < min { continue } if strings.Contains(origin, text) { if !onlyShowFileName { fmt.Printf("file:%s line:%d matched:%s\n", filepath, count, origin) } else { fmt.Printf("file:%s line:%d\n", filepath, count) } } if err != nil { break } } } for _, v := range data { if v.IsDir() { searchText(filepath.Join(folder, v.Name()), filematch, text, thread, autoGBK, stMax, stMin, onlyShowFileName) } filepath := filepath.Join(folder, v.Name()) if matched, _ := regexp.MatchString(filematch, filepath); matched { wg.Add(1) go searchFn(filepath, text) } } wg.Wait() return nil }