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コマンドを使用して、たとえば標準出力を2つの列に印刷できます。

$ 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

いつでも使用できますが、mktempPOSIXはそれを指定せず、それに関連するいくつかの競合条件があります。

いつでも次のようにwhileループを使用できますが、シェルのループは非常に遅いです。

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

賞金のお知らせには、次の内容が記載されています。

答えは、行数が異なる変数のすべてのシェルで機能し、動的変数の数に対して機能する必要があります。

厳密に言えば、「すべてのシェルで作業する」ことは不可能です。ほとんどできないことです。何もないCシェルシリーズのシェルとBourneシェル/ POSIXシェルシリーズで作業することは非常に重要です。私はbash、dash(かなり基本的なPOSIX互換シェル)、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互換シェルで動作します1。 2番目のアプローチ()はすべてではありませんが、ほとんどのPOSIX互換シェル2で動作しますが、部分はそうではないかもしれません。いくつかの古いシェルは認識されないかもしれません(完全に?)bashismです。`echo word1 ; echo word2`echo -e 'word3\nword4'$(…)$'…'

解決策がパターンに従う変数名に依存しないことを示すために、意図的に一貫したパターンを持たない変数名を使用します。必要に応じて  、、、  varAおよび   (  または、、、  および)を   使用できます。varBvarCvarDvar1var2var3var4

ソリューションを実行します。

$ ./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準拠シェルには名前付き配列がないため、すべてサポートされている1つの(名前なし)配列である引数リストを使用します。
  • for a in "$@"ループに入ると、それ値のリストは、シェルの引数リストを変更できるように設定されます。
  • 最初のステップでは、破損したシェルのパラメータリストが使用されますset --
  • 通り過ぎるたびに、
    • 一時ファイルを作成します。移植性を高めるために`mktemp`代わりに使用してください  $(mktemp)(そうでなければ$(…)一般的に好ましい)。
    • パラメータリストにファイル名を追加します。
    • 現在のパラメータを書き込みます(例:オリジナルパラメータリスト)をファイルに追加します。printfこれがすべてのシェルで組み込みコマンドとして使用可能かどうかはわかりませんが、書くことができる (組み込みコマンドまたは外部実行プログラムとして)博物館の外部のすべてのシステムにインストールされます。システムにない場合はprintf試してみることができますが、echo次に始まる文字列に注意してください。-または含めます\
  • prファイルリストから呼び出されます。
    • -Tページングを防ぐには、Steeldriver OKを使用してください。
    • ファイルをマージする-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 その幅の列に列区切りのためのスペース2つを加えたものです。

____________
1しかし、\前にaを入力しない限り、Cシェルはそれを許可しませんEnter
2  Cシェルはこれを許可しますが、改行文字をスペースに変換します。 (私が逃した部分があるかもしれません。)

答え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

関連情報