You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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内容,代码重复度高。

修复方案

  1. 调整函数传参:把main函数中创建好的输出文件句柄传给生成ASCII内容的函数,去掉重复打开文件的逻辑,同时删除硬编码的输出文件名。
  2. 重构多行处理逻辑:每遍历到拆分后的一行文本,就重新初始化存储ASCII行的结构,处理完当前行所有字符后,立刻把8行ASCII内容写入输出,再处理下一行,实现换行分段效果。
  3. 补全边界处理:遇到拆分后的空字符串(对应连续换行),直接写入空行即可。
  4. 清理冗余代码:用二维切片替代8个独立的字符串切片,减少重复代码;删除多余的单独判断\n的分支,统一在处理逻辑里覆盖。
  5. 补全异常分支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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.29 04:18:14