【问题标题】:How to horizontally mirror ascii art?如何水平镜像ascii艺术?
【发布时间】:2012-10-18 05:43:50
【问题描述】:

所以...我知道我可以使用tac 或其他一些工具来反转文件中的行顺序,但是如何在另一个维度(即水平方向)中重新排序?我正在尝试使用以下 awk 脚本:

{
    out="";
    for(i=length($0);i>0;i--) {
        out=out substr($0,i,1)}
    print out;
}

这似乎颠倒了字符,但它是乱码,我不明白为什么。我错过了什么?

我在 awk 中执行此操作,但有更好的方法吗? sed,也许?

这是一个例子。输入数据如下所示:

$ cowsay <<<"hello"
 _______
< hello >
 -------
        \   ^__^
         \  (oo)\_______
            (__)\       )\/\
                ||----w |
                ||     ||

输出如下:

$ cowsay <<<"hello" | rev
_______ 
> olleh <
------- 
^__^   \        
_______\)oo(  \         
\/\)       \)__(            
| w----||                
||     || 

请注意,无论我使用 rev 还是我自己的 awk 脚本,输出都是相同的。正如你所看到的,事情已经逆转了,但是......它被破坏了。

【问题讨论】:

  • 视情况而定,你的机器有rev命令吗?
  • 嗯,rev 似乎在那里,但文件乱码的方式与我的 awk 脚本一样。
  • 可能会有一些奇怪的换行符?
  • 不,它只是一个标准文本文件,带有普通的 unix EOL。我会做一个例子并将其添加到问题中。
  • 是的,我认为这将是必要的。我唯一能想到的是不对称字符,例如&gt;,当线条颠倒时不会被&lt;替换,而当图像的其余部分被镜像时看起来很奇怪。

标签: image-processing awk ascii-art


【解决方案1】:

rev 很好,但它不会填充输入行。它只是颠倒它们。

您看到的“混乱”是因为一行可能有 20 个字符长,而下一行可能有 15 个字符长。在您的输入文本中,它们共享一个左侧列。但在您的输出文本中,它们需要共享右侧列。

所以你需要填充。哦,还有不对称反转,正如 Joachim 所说。

这是我的revawk

#!/usr/bin/awk -f

# 
length($0)>max {
    max=length($0);
}

{
    # Reverse the line...
    for(i=length($0);i>0;i--) {
        o[NR]=o[NR] substr($0,i,1);
    }
}

END {
    for(i=1;i<=NR;i++) {
        # prepend the output with sufficient padding
        fmt=sprintf("%%%ds%%s\n",max-length(o[i]));
        printf(fmt,"",o[i]);
    }
}

(我在gawk 中这样做了;我认为我没有使用任何傻瓜,但如果您使用的是更经典的awk 变体,您可能需要调整它。)

使用与使用 rev 相同的方式。

ghoti@pc:~$ echo hello | cowsay | ./revawk | tr '[[]()<>/\\]' '[][)(><\\/]'
                    _______ 
                   < olleh >
                    ------- 
            ^__^   /        
    _______/(oo)  /         
/\/(       /(__)            
   | w----||                
   ||     ||                

如果您愿意这样做,您甚至可以在 awk 脚本中运行翻译,方法是将其添加到最后的 printf 行:

        printf(fmt," ",o[i]) | "tr '[[]()<>/\\]' '[][)(><\\/]'";

但我不推荐它,因为它会使revawk 命令对其他应用程序的用处不大。

【讨论】:

  • +1 然而,唯一的问题是你的牛现在看起来更像一匹马了。马说:-)
  • 啊,我明白你的意思了。这是因为printf("%0s"," ") 的长度为 1,而不是 0。更新了我的答案。 :-)
【解决方案2】:

你的线条长度不一样,所以把牛倒过来会折断它。您需要做的是将线“填充”为相同的长度,然后反转。

例如;

cowsay <<<"hello" | awk '{printf "%-40s\n", $0}' | rev

将其填充到 40 列,然后反转。

编辑:@ghoti 做了一个脚本,肯定会击败这个简单的反向,看看他的答案。

【讨论】:

  • 我赞成您的回答,部分原因是您提出了不对称字符反转的想法。谢谢!
【解决方案3】:

这是使用GNU awkrev 的一种方法

运行方式:

awk -f ./script.awk <(echo "hello" | cowsay){,} | rev

script.awk的内容:

FNR==NR {
    if (length > max) {
        max = length
    }
    next
}

{
    while (length < max) {
        $0=$0 OFS
    }
}1

或者,这里是单行:

awk 'FNR==NR { if (length > max) max = length; next } { while (length < max) $0=$0 OFS }1' <(echo "hello" | cowsay){,} | rev

结果:

                    _______ 
                   > olleh <
                    ------- 
            ^__^   \        
    _______\)oo(  \         
\/\)       \)__(            
   | w----||                
   ||     ||                

----------------------------------------------- -----------------------------------------------------------

这是另一种使用GNU awk的方法:

运行方式:

awk -f ./script.awk <(echo "hello" | cowsay){,}

script.awk的内容:

BEGIN {
    FS=""
}

FNR==NR { 
    if (length > max) {
        max = length
    }
    next
}

{
    while (length < max) {
        $0=$0 OFS
    }
    for (i=NF; i>=1; i--) {
        printf (i!=1) ? $i : $i ORS
    }
}

或者,这里是单行:

awk 'BEGIN { FS="" } FNR==NR { if (length > max) max = length; next } { while (length < max) $0=$0 OFS; for (i=NF; i>=1; i--) printf (i!=1) ? $i : $i ORS }' <(echo "hello" | cowsay){,}

结果:

                    _______ 
                   > olleh <
                    ------- 
            ^__^   \        
    _______\)oo(  \         
\/\)       \)__(            
   | w----||                
   ||     ||                

----------------------------------------------- -----------------------------------------------------------

解释:

这是对第二个答案的解释。我假设有awk的基本知识:

FS=""                 # set the file separator to read only a single character
                      # at a time.

FNR==NR { ... }       # this returns true for only the first file in the argument
                      # list. Here, if the length of the line is greater than the
                      # variable 'max', then set 'max' to the length of the line.
                      # 'next' simply means consume the next line of input

while ...             # So when we read the file for the second time, we loop
                      # through this file, adding OFS (output FS; which is simply
                      # a single space) to the end of each line until 'max' is
                      # reached. This pad's the file nicely.

for ...               # then loop through the characters on each line in reverse.
                      # The printf statement is short for ... if the character is
                      # not at the first one, print it; else, print it and ORS.
                      # ORS is the output record separator and is a newline.

您可能需要了解的其他一些事项:

{,} 通配符后缀是重复输入文件名两次的简写。 不幸的是,它不是标准的 Bourne shell。但是,您可以改为使用:

<(echo "hello" | cowsay) <(echo "hello" | cowsay)

另外,在第一个示例中,{ ... }1{ ... print $0 } 的缩写

HTH。

【讨论】:

  • 谢谢!我看到这行得通,但我不明白。我注意到您正在使用仅限 bash 的东西。将{,} 添加到文件名有什么作用?那是特定于 bash 的,还是可以在 Bourne shell 中工作?虽然我的示例是 bash,但我打算在 Bourne 中运行一些东西,因为这是 FreeBSD 中 crontab 的默认 shell。
  • @Graham:我添加了一些 cmets。 HTH。如果您需要更多帮助,请告诉我。干杯。
  • @Graham, {,} 是两次写入文件名的可爱简写——它是 bash 大括号扩展(查看手册页)。 Wrt cron,你可以调用一个可以用任何语言编写的外部脚本——不要试图在实际的 crontab 中塞进太多东西并牺牲可读性。
  • 我明白了。史蒂夫,非常感谢您提供如此完善的答案。我的一个赞成票似乎不值得你为此付出努力。不幸的是,“cowsay”输出是一个例子,我正在处理的实际输出是由一个我每次运行只想运行一次的脚本生成的,所以我将采用 ghoti 的答案。
  • 别担心,伙计。我也最喜欢ghoti的回答。谢谢!
【解决方案4】:

您也可以使用 bash、coreutils 和 sed 来实现(要使其与 zsh 一起使用,需要将 while 循环包装在 tr ' ' '\x01' | while ... | tr '\x01' ' ' 中,尚不知道为什么):

say=hello
longest=$(cowsay "$say" | wc -L)

echo "$say" | rev | cowsay | sed 's/\\/\\\\/g' | rev |
  while read; do printf "%*s\n" $longest "$REPLY"; done |
  tr '[[]()<>/\\]' '[][)(><\\/]'

输出:

                    _______ 
                   < hello >
                    ------- 
            ^__^   /        
    _______/(oo)  /         
/\/(       /(__)            
   | w----||                
   ||     ||                

这样最后会留下很多多余的空格,追加| sed 's/ *$//'去掉。

说明

需要引用 cowsay 输出,尤其是 sed 通过复制它们来处理的反斜杠。使用printf '%*s' len str 获得正确的线宽,它使用len 作为字符串长度参数。最后,不对称字符被对应的字符替换,如 ghoti 的 回答中所做的那样。

【讨论】:

  • 你好雷神。不幸的是,在 FreeBSD 上,wc 命令没有-L 选项。不过感谢您的回答(我 +1),我喜欢您对文本反转的处理。 :)
  • wc -L 返回其输入中最长行的长度。也可以从例如:awk '{print length}' | sort -rn | head -n1
  • 嗯,FreeBSD 的 wc 似乎在 7.x 版本中的某个时候获得了 -L 选项,所以最近的 FreeBSD 有它,但旧的没有。而且似乎即使是最近的 OSX 也没有。谢谢指点。
【解决方案5】:

我不知道您是否可以在 AWK 中执行此操作,但以下是所需的步骤:

确定原件最长线的长度,您需要它为任何较小的线提供适当的间距。

    (__)\       )\/\

对于每行的最后一个字符,根据您从第一步中获得的内容来规划行首空格的需求。

< hello >
//Needs ??? extra spaces, because it ends right after '>'.
//It does not have spaces after it, making it miss it's correct position after reverse.
        (__)\       )\/\
< hello >???????????????

对于每一行,应用该行所需的空格数,然后按相反顺序添加原始字符。

                    _______ 
                   > olleh <
                    ------- 
            ^__^   \        
    _______\)oo(  \         
\/\)       \)__(            
   | w----||                
   ||     || 

最后,将所有非水平对称的字符替换为其水平相反的字符。 (&lt;&gt;[] 等)

                    _______ 
                   < olleh >
                    ------- 
            ^__^   /        
    _______/(oo)  /         
/\/(       /(__)            
   | w----||                
   ||     || 

需要注意的两件事:

  • 如您所见,文本在还原时不会正确。
  • $%&amp; 等字符不是水平对称的, 但也可能没有相反的情况,除非您使用专门的 Unicode 块。

【讨论】:

    【解决方案6】:

    我会说您可能需要将每行固定为列宽,以便每行具有相同的长度。因此,如果第一行是一个字符后跟一个 LF,则需要在反转之前用空格填充反转。

    【讨论】:

      猜你喜欢
      • 2013-06-27
      • 1970-01-01
      • 2016-01-04
      • 2011-11-14
      • 2016-10-12
      • 2010-12-14
      • 1970-01-01
      • 1970-01-01
      • 2010-09-28
      相关资源
      最近更新 更多