package main import ( "bufio" "flag" "fmt" "log" "os" "regexp" "strconv" "strings" ) func printUsage() { fmt.Fprintf(os.Stderr, "Usage: %s [options] [file]\n", os.Args[0]) fmt.Fprintln(os.Stderr, "Options:") flag.PrintDefaults() fmt.Fprintln(os.Stderr, "\nRange specification examples:") fmt.Fprintln(os.Stderr, "10,20 Reads lines 10 to 20 from the input") fmt.Fprintln(os.Stderr, "15:+5 Reads 5 lines starting from line 15") fmt.Fprintln(os.Stderr, "3 Reads from line 3 to the end of the file") fmt.Fprintln(os.Stderr, "+2 Reads the first 2 lines") fmt.Fprintln(os.Stderr, "Please ensure you input valid range specifications and file paths.") os.Exit(1) } func parseArgs(args []string) (int, int, string, error) { var filename string var start, length, end int found := false // Concatenate all arguments to a single string for easier parsing joinedArgs := strings.Join(args, " ") // Regular expression to match ranges and numbers rangeRegex := regexp.MustCompile(`((\d+)?([ :,;-]))(\+)?(\d+)`) range2Regex := regexp.MustCompile(`((\d+)([ :,;-]))`) range3Regex := regexp.MustCompile(`(\+)?(\d+)`) matches := rangeRegex.FindStringSubmatch(joinedArgs) if matches != nil { //check if start was defined if matches[2] != "" { start, _ = strconv.Atoi(matches[2]) // Convert start line to integer } else { start = 1 } if matches[4] == "+" { // Check if it's a relative length length, _ = strconv.Atoi(matches[5]) // Convert length to integer end = start + length - 1 } else { end, _ = strconv.Atoi(matches[5]) // Convert end line to integer } // Remove the matched part from the arguments joinedArgs = strings.Replace(joinedArgs, matches[0], "", 1) found = true } else { matches = range2Regex.FindStringSubmatch(joinedArgs) if matches != nil { start, _ = strconv.Atoi(matches[2]) // Convert start line to integer end = -1 // Remove the matched part from the arguments joinedArgs = strings.Replace(joinedArgs, matches[0], "", 1) found = true } else { matches = range3Regex.FindStringSubmatch(joinedArgs) if matches != nil { if matches[1] == "+" { // Check if it's a relative length length, _ := strconv.Atoi(matches[2]) // Convert length to integer start = 1 end = start + length - 1 } else { // Otherwise convert it to an absolute length start, _ = strconv.Atoi(matches[2]) // Convert start line to integer end = -1 // Remove the matched part from the arguments } joinedArgs = strings.Replace(joinedArgs, matches[0], "", 1) found = true } else { found = false printUsage() } } } // Clean up and identify the filename, if present joinedArgs = strings.TrimSpace(joinedArgs) if joinedArgs != "" && !found { // If we didn't find numbers, interpret the remaining as filename filename = joinedArgs } else if joinedArgs != "" { // Otherwise, interpret any non-empty remaining part as filename filename = joinedArgs } if !found { return 0, 0, "", fmt.Errorf("no valid range or line number found") } return start, end, filename, nil } func main() { flag.Parse() args := flag.Args() if len(args) < 1 { printUsage() os.Exit(1) } startIndex, endIndex, filename, err := parseArgs(args) if err != nil { fmt.Fprintf(os.Stderr, "Error parsing arguments: %v\n", err) os.Exit(1) } // Detemine the input source (file or stdin) inputSource := os.Stdin if filename != "" { inputSource, err = os.Open(filename) if err != nil { log.Fatalf("Failed to open file: %v\n", err) } defer inputSource.Close() } scanner := bufio.NewScanner(inputSource) //print all lines from line buner start to linenumber end unless linenumber end is -1 for i := 1; scanner.Scan(); i++ { if startIndex <= i && (i <= endIndex || endIndex == -1) { fmt.Println(scanner.Text()) } } if err := scanner.Err(); err != nil { log.Fatal(err) } }