如何在 sh 中并排列中打​​印多行变量?

如何在 sh 中并排列中打​​印多行变量?

这个问题是基于Ask Ubuntu 上有类似的问题,但bash我希望在 中有类似的输出sh

bash 中没有问题;它按预期工作。

wolf@linux:~$ echo $SHELL
/usr/bin/bash
wolf@linux:~$ 
wolf@linux:~$ varA='Aug 01
> Aug 16
> Aug 26'
wolf@linux:~$ 
wolf@linux:~$ varB='04:25
> 03:39
> 10:06'
wolf@linux:~$ 
wolf@linux:~$ echo "$varA $varB"
Aug 01
Aug 16
Aug 26 04:25
03:39
10:06
wolf@linux:~$
wolf@linux:~$ paste <(printf %s "$varA") <(printf %s "$varB")
Aug 01  04:25
Aug 16  03:39
Aug 26  10:06
wolf@linux:~$

但是,当我在 中尝试类似的命令时sh,出现以下错误。

wolf@linux:~$ sh
$ 
$ varA='Aug 01
> Aug 16
> Aug 26'

$ varB='04:25
> 03:39
> 10:06'
$ 
$ echo "$varA $varB"
Aug 01
Aug 16
Aug 26 04:25
03:39
10:06
$ 
$ paste <(printf %s "$varA") <(printf %s "$varB")
sh: 22: Syntax error: "(" unexpected
$ 

是否有可能获得类似的输出sh

答案1

如果您的变量具有相同的行数那么您可以使用该pr命令将标准输出打印到两列中,例如。

$ printf '%s\n' "$varA" "$varB" | pr -2 -Ts^I       
Aug 01  04:25
Aug 16  03:39
Aug 26  10:06

其中^I代表制表符(与命令的默认分隔符一致paste),可以使用组合键Ctrl+引入V TAB,同时-T关闭页眉和页脚。

答案2

您始终可以使用它mktemp,但 POSIX 未指定它,并且存在一些与之相关的竞争条件。

您始终可以使用像这样的 while 循环,但请记住 shell 中的循环非常慢:

counter=1
while true
do
    left="$(echo "$varA" | awk -v cnt="$counter" 'NR==cnt {printf $0}')"
    right="$(echo "$varB" | awk -v cnt="$counter" 'NR==cnt {printf $0}')"
    if [ -z "$left" ] && [ -z "$right" ]
    then
        break
    fi

    echo "$left $right"
    counter=$((counter + 1))
done

输出:

Aug 01 04:25
Aug 16 03:39
Aug 26 10:06

如果需要,您可以添加更多变量,但它不能满足 应该适用于动态数量的变量要求,因为您必须手动添加它们。当然,您可以在 awk 中实现所有内容。

答案3

赏金通知上写着

对于行数不同的变量,答案应该适用于所有 shell,并且应该适用于动态数量的变量

严格来说,“在所有 shell 上工作”是不可能的。这几乎是不可能做到的任何事物它在 C shell 系列 shell 和 Bourne shell / POSIX 系列 shell 中工作非常重要。我在 bash、dash(这是一个相当基本的 POSIX 兼容 shell)、ash 和 zsh 中测试了以下内容。

以下是设置多行变量的几种方法:

var17='The
quick
brown
fox'

var42=`echo jumps; echo over; echo -e 'the\nlazy\ndog.'`

another_var=$(printf '%s\n' And they lived happily ever after.)

yet_another=$'The\nend.'

第一种方法(实际上Enter在引号内输入)应该适用于大多数(如果不是全部)符合 POSIX 的 shell 1。第二种方法 ( ) 应该适用于大多数(如果不是全部)符合 POSIX 的 shell 2,尽管 ) 部分可能不会。一些旧的 shell 可能无法识别,并且(完全?)是一种 bashism。`echo word1 ; echo word2`echo -e 'word3\nword4'$(…)$'…'

我故意使用没有一致模式的变量名称,以证明该解决方案不依赖于遵循模式的变量名称。如果需要,您可以使用 varA,  varB,  varC and  varD (或var1,  var2,  var3 and  )。var4

运行解决方案:

$ ./myscript1 "$var17" "$var42" "$another_var" "$yet_another"
The               jumps             And               The
quick             over              they              end.
brown             the               lived
fox               lazy              happily
                  dog.              ever
                                    after.

请注意显而易见的事情:字符串具有不同的行数,并且行具有不同的字符数。我们不必须 使用变量;我们可以直接在命令行上输入多行字符串值:

$ ./myscript1 "$var17" "$var42" "$another_var"   $'The\nend.'
The               jumps             And               The
quick             over              they              end.
brown             the               lived
fox               lazy              happily
                  dog.              ever
                                    after.

这是myscript1

#!/bin/dash
first=1
for a in "$@"
do
        if [ "$first" = 1 ]
        then
                set --
                first=
        fi
        file=`mktemp`
        set -- "$@" "$file"
        printf '%s\n' "$a" > "$file"
done
pr -T -m "$@"

rm "$@"

笔记:

  • 由于许多符合 POSIX 标准的 shell 没有命名数组,因此我们使用它们都支持的一个(未命名)数组:参数列表。
  • 一旦我们进入for a in "$@"循环, 它是值列表已设置,因此我们可以更改 shell 的参数列表。
  • 第一次通过时,用 破坏 shell 的参数列表set --
  • 每次经过,
    • 创建一个临时文件。使用 `mktemp`而不是$(mktemp)为了增加便携性(否则$(…)通常是首选)。
    • 将文件名添加到参数列表中。
    • 写入当前参数(即,来自原来的参数列表)到文件。我不知道是否printf可以在所有 shell 中作为内置命令使用,但应该是可用的 (作为内置命令或外部可执行程序)在博物馆外的所有系统上。如果您的系统没有printf,您可以尝试使用echo,但要注意以开头的字符串-或包含\.
  • pr在文件列表上 调用。
    • 使用-TSteeldriver 确定的 来防止分页。
    • 用于-m合并文件(即在多列模式下)。
  • 最后,删除所有文件。

这看起来像是将列分隔了很多空间,但情况并非总是如此:

$ ./myscript1 "A very long string about a quick brown fox and a lazy dog" .
A very long string about a quick br .

发生的事情是:

  • pr计算文件数量;即列数。我们就这样称呼它吧名词
  • 它将线宽除以,向下舍入。 (线宽默认为 72。)
  • 它创建n列,每列 ⌊ 72/n ⌋ 字符位置较宽。比该值更窄的值会被填充;比该值更宽的值将被截断。

您可能更愿意避免空格和截断。我们可以这样做,因为我们可以pr使用以下 选项指定线宽-w

$ ./myscript2 "A very long string about a quick brown fox and a lazy dog" .
A very long string about a quick brown fox and a lazy dog  .

这是myscript2

#!/bin/dash
if [ "$#" = 0 ]
then
        printf '%s\t%s\n' "Usage:" "$0 [string] ..."
        exit 1
fi
first=1
maxwidth=0
for a
do
        if [ "$first" = 1 ]
        then
                set --
                first=
        fi
        file=`mktemp`
        set -- "$@" "$file"
        printf '%s\n' "$a" > "$file"
        width=`printf '%s\n' "$a" | wc -L`
        if [ "$width" -gt "$maxwidth" ]
        then
                maxwidth="$width"
        fi
done
linewidth=`expr "$#" "*" "(" "$maxwidth" + 2 ")"`
pr -T -w "$linewidth" -m "$@"

rm "$@"

笔记:

  • for a是 的缩写for a in "$@"。这适用于 ash、bash、dash、zsh 以及其他可能的系统。
  • wc -L查找输入中的最大行长度。不幸的是,这是一个 GNU 扩展。这可以使用 POSIX 工具来完成,但并不那么简单。
  • 循环找到任何输入中最长的行......
  • …然后我们计算足以容纳的线宽n 该宽度的列,加上两个用于列分隔的空格。

____________
1 但是 C shell 不允许这样做,除非您\在 之前键入 a Enter
2  C shell 允许这样做,但它将换行符转换为空格。 (我可能遗漏了一些东西。)

答案4

1. 命令paste

paste /dev/fd/3 3<<-EOF /dev/fd/4 4<<-EOF
> $varA
> EOF
> $varB
> EOF

输出 :

Aug 01  04:25
Aug 16  03:39
Aug 26  10:06

或者,您也可以将粘贴与临时文件一起使用,但过程很漫长 temp_file=$(mktemp)


2. 命令pr

printf '%s\n' "$varA" "$varB" | pr -2 -t -s

输出:

Aug 01  04:25
Aug 16  03:39
Aug 26  10:06

相关内容