Golang实现ASCII艺术生成工具时\n转义字符不生效问题求解
ASCII艺术字生成工具换行问题修复
问题说明
实现命令行版ASCII艺术文本生成工具时,预期调用格式如下:go run . "Hello World!" standard --output="banner.txt"
standard参数指定生成ASCII艺术使用的figlet字体--output参数指定将生成结果写入对应名称的文件
当前执行带转义换行的命令go run . "Hello\nThere" standard --output="banner.txt"时,输入字符串中的\n无法被正确识别为换行,无法输出多行ASCII艺术效果。
原有实现代码如下:
package main import ( "bufio" "fmt" "os" "strings" ) func main() { if len(os.Args) == 4 { args := os.Args arg3 := os.Args[3] arg4 := strings.Split(arg3, "=") if arg4[0] == "--output" { file, err := os.Create(arg4[1]) if err != nil { fmt.Println(err) return } defer file.Close() police := "" if args != nil && len(args) > 1 { arg2 := os.Args[2] if arg2 == "standard" { police = "standard.txt" } else if arg2 == "shadow" { police = "shadow.txt" } else if arg2 == "thinkertoy" { police = "thinkertoy.txt" } else if arg2 == "graffiti" { police = "graffiti.txt" } else { fmt.Println("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~") fmt.Println(" Figlet name invalid. Available : ") fmt.Println(" - graffiti") fmt.Println(" - standard") fmt.Println(" - shadow") fmt.Println(" - thinkertoy") } } fichier, err := os.Open(police) if err != nil { fmt.Println("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~") return } scanner := bufio.NewScanner(fichier) // Scan le fichier scanner.Split(bufio.ScanLines) // split le scan en lignes var lignes []string // englobe toutes les lignes for scanner.Scan() { lignes = append(lignes, scanner.Text()) } asciiChrs := make(map[int][]string) // mise en place de la map (vide pour l'instant) dec := 31 // correspond a la case zero de la map for _, ligne := range lignes { //ligne = une/chaques ligne dans l'intervalle lignes du fichier txt if ligne == "" { dec++ } else { asciiChrs[dec] = append(asciiChrs[dec], ligne) } } userInput := os.Args[1] invCharCount := 0 for _, invalidChar := range userInput { if invalidChar < 31 || invalidChar > 126 { invCharCount++ fmt.Println("Caractère non supporté: ", string(invalidChar)) } } if invCharCount > 0 { fmt.Println("Try Again") os.Exit(0) } else if userInput == "\\n" { fmt.Print("\n") return } else if userInput == "" { return } else { Newline(userInput, asciiChrs) } } else { fmt.Println("Usage: go run . [STRING] [BANNER]\n\nEX: go run . something standard") } } } func Newline(n string, y map[int][]string) { replaceNewline := strings.Replace(n, "\\n", "\n", -1) wordsSlice := strings.Split(replaceNewline, "\n") slice := []string{} slice2 := []string{} slice3 := []string{} slice4 := []string{} slice5 := []string{} slice6 := []string{} slice7 := []string{} slice8 := []string{} for _, mot := range wordsSlice { //for j := 0; j < 8; /*lignes*/ j++ { for _, lettre := range mot { //fmt.Print(y[int(lettre)][j]) slice = append(slice, (y[int(lettre)][0])) slice2 = append(slice2, (y[int(lettre)][1])) slice3 = append(slice3, (y[int(lettre)][2])) slice4 = append(slice4, (y[int(lettre)][3])) slice5 = append(slice5, (y[int(lettre)][4])) slice6 = append(slice6, (y[int(lettre)][5])) slice7 = append(slice7, (y[int(lettre)][6])) slice8 = append(slice8, (y[int(lettre)][7])) } fmt.Println() // } } f, err := os.OpenFile("banner.txt", os.O_APPEND|os.O_WRONLY, 0600) if err != nil { panic(err) } defer f.Close() outpout := strings.Join(slice, "") outpout2 := strings.Join(slice2, "") outpout3 := strings.Join(slice3, "") outpout4 := strings.Join(slice4, "") outpout5 := strings.Join(slice5, "") outpout6 := strings.Join(slice6, "") outpout7 := strings.Join(slice7, "") outpout8 := strings.Join(slice8, "") if _, err = f.WriteString(outpout); err != nil { panic(err) } if _, err = f.WriteString("\n"); err != nil { panic(err) } if _, err = f.WriteString(outpout2); err != nil { panic(err) } if _, err = f.WriteString("\n"); err != nil { panic(err) } if _, err = f.WriteString(outpout3); err != nil { panic(err) } if _, err = f.WriteString("\n"); err != nil { panic(err) } if _, err = f.WriteString(outpout4); err != nil { panic(err) } if _, err = f.WriteString("\n"); err != nil { panic(err) } if _, err = f.WriteString(outpout5); err != nil { panic(err) } if _, err = f.WriteString("\n"); err != nil { panic(err) } if _, err = f.WriteString(outpout6); err != nil { panic(err) } if _, err = f.WriteString("\n"); err != nil { panic(err) } if _, err = f.WriteString(outpout7); err != nil { panic(err) } if _, err = f.WriteString("\n"); err != nil { panic(err) } if _, err = f.WriteString(outpout8); err != nil { panic(err) } }
核心问题点
- 换行处理逻辑错误:虽然做了字面量
\n到真实换行符的替换,但是拆分多行之后,没有按行单独存储ASCII内容,所有行的字符都堆在全局的8个切片里,遍历拆分后的行时只空打印了换行,没有把每段文本对应的ASCII块分开输出。 - 输出逻辑硬编码:前面已经解析了
--output参数拿到自定义输出文件名,也创建了对应文件句柄,后面写文件时又硬编码打开banner.txt追加内容,自定义输出路径不生效。 - 边界场景缺失:只判断了输入完全等于
\n的情况,没有处理文本中间夹换行、连续换行(空行)的场景。 - 冗余逻辑过多:定义8个独立切片存储8行ASCII内容,代码重复度高。
修复方案
- 调整函数传参:把main函数中创建好的输出文件句柄传给生成ASCII内容的函数,去掉重复打开文件的逻辑,同时删除硬编码的输出文件名。
- 重构多行处理逻辑:每遍历到拆分后的一行文本,就重新初始化存储ASCII行的结构,处理完当前行所有字符后,立刻把8行ASCII内容写入输出,再处理下一行,实现换行分段效果。
- 补全边界处理:遇到拆分后的空字符串(对应连续换行),直接写入空行即可。
- 清理冗余代码:用二维切片替代8个独立的字符串切片,减少重复代码;删除多余的单独判断
\n的分支,统一在处理逻辑里覆盖。 - 补全异常分支return:用户输入无效字体名、参数格式错误时,打印提示后直接返回,不要继续执行后续逻辑。
修复后的核心实现代码:
package main import ( "bufio" "fmt" "os" "strings" ) func main() { if len(os.Args) != 4 { fmt.Println("Usage: go run . [STRING] [BANNER] --output=[FILENAME]\n\nEX: go run . \"Hello World\" standard --output=banner.txt") return } arg3 := os.Args[3] arg4 := strings.SplitN(arg3, "=", 2) if len(arg4) != 2 || arg4[0] != "--output" { fmt.Println("Usage: go run . [STRING] [BANNER] --output=[FILENAME]\n\nEX: go run . \"Hello World\" standard --output=banner.txt") return } outPath := arg4[1] file, err := os.Create(outPath) if err != nil { fmt.Println("Create output file failed:", err) return } defer file.Close() fontFile := "" switch os.Args[2] { case "standard": fontFile = "standard.txt" case "shadow": fontFile = "shadow.txt" case "thinkertoy": fontFile = "thinkertoy.txt" case "graffiti": fontFile = "graffiti.txt" default: fmt.Println("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~") fmt.Println(" Figlet name invalid. Available : ") fmt.Println(" - graffiti") fmt.Println(" - standard") fmt.Println(" - shadow") fmt.Println(" - thinkertoy") return } fichier, err := os.Open(fontFile) if err != nil { fmt.Println("Load font file failed:", err) return } defer fichier.Close() scanner := bufio.NewScanner(fichier) var lignes []string for scanner.Scan() { lignes = append(lignes, scanner.Text()) } asciiChrs := make(map[int][]string) dec := 31 for _, ligne := range lignes { if ligne == "" { dec++ } else { asciiChrs[dec] = append(asciiChrs[dec], ligne) } } userInput := os.Args[1] for _, invalidChar := range userInput { if invalidChar != '\\' && invalidChar != 'n' && (invalidChar < 32 || invalidChar > 126) { fmt.Println("Unsupported character: ", string(invalidChar)) os.Exit(0) } } GenerateASCII(userInput, asciiChrs, file) } func GenerateASCII(input string, charMap map[int][]string, outFile *os.File) { // 替换字面量\n为真实换行符 processedInput := strings.ReplaceAll(input, "\\n", "\n") lineList := strings.Split(processedInput, "\n") writer := bufio.NewWriter(outFile) defer writer.Flush() for _, lineContent := range lineList { // 空行直接写入换行 if lineContent == "" { writer.WriteString("\n") continue } // 初始化8行存储结构 asciiLines := make([][]string, 8) for i := range asciiLines { asciiLines[i] = make([]string, 0) } // 拼接当前行每个字符的ASCII内容 for _, c := range lineContent { charArt, exist := charMap[int(c)] if !exist { continue } for i := 0; i < 8; i++ { asciiLines[i] = append(asciiLines[i], charArt[i]) } } // 写入当前行的8行ASCII内容 for i := 0; i < 8; i++ { writer.WriteString(strings.Join(asciiLines[i], "") + "\n") } } }
修复后执行带\n的命令时,会自动在换行位置拆分文本,分别生成每段的ASCII艺术块,写入指定输出文件。
内容的提问来源于stack exchange,提问作者lemon_42
相关产品推荐
相关产品推荐

