web-dev-qa-db-ja.com

バッシュ-可変長の線の後ろに垂直線を描画します

次の形式のテキストファイルがあり、これらの行の後に縦線を追加し、その後に数字を増やします。

c4-1 d e c
c d e c
e-2 f g2
e4 f g2
g8-4\( a-5 g f\) e4 c
g'8\( a g f\) e4 c
c-1 r c2
c4 r c2 

次のwhile-loopを使用して、行と番号付けを行います。

#!/bin/bash

while read -r line; do
    if [ -z "$line" ]; then
        echo
        continue
    fi
    n=$((++n)) \
    && grep -vE "^$|^%" <<< "$line" \
    | sed 's/$/\ \|\ \%'$(("$n"))'/'
done < file

次のような出力を取得します。

c4-1 d e c | %1
c d e c | %2
e-2 f g2 | %3
e4 f g2 | %4
g8-4\( a-5 g f\) e4 c | %5
g'8\( a g f\) e4 c | %6
c-1 r c2 | %7
c4 r c2 | %8

ここで、アディションを垂直方向に揃えて、次のような出力を取得します。

c4-1 d e c            | %1
c d e c               | %2
e-2 f g2              | %3
e4 f g2               | %4
g8-4\( a-5 g f\) e4 c | %5
g'8\( a g f\) e4 c    | %6
c-1 r c2              | %7
c4 r c2               | %8

これは、どういうわけか、最長の行の行の長さ(ここでは21文字)と各行の行の長さを取得し、スペースとの差を追加する必要があることを意味します。どうすればこれを達成できますか?

9
nath

整列せずに行を印刷し、column -tおよびダミーの区切り文字を使用して出力をフォーマットできます。

#!/bin/bash

while read -r line; do
  if [ -z "$line" ]; then
    echo
    continue
  fi
  printf '%s@| %%%s\n' "$line" "$((++n))"
done < file | column -e -s'@' -t | sed 's/ |/|/'

ここでは、@の前に|をダミー文字として追加して、列の終わりを示しています。最後のsedコマンドは、|の前にある1つの追加の空白文字を削除するために使用されます。オプション-eは、出力の空の行を維持するために必要です。

出力:

c4-1 d e c            | %1
c d e c               | %2
e-2 f g2              | %3
e4 f g2               | %4
g8-4\( a-5 g f\) e4 c | %5
g'8\( a g f\) e4 c    | %6
c-1 r c2              | %7
c4 r c2               | %8
10
Freddy

awk + GNU wcを使用すると、入力内のすべての文字がシングル幅であると想定します:

$ awk -v f="$(wc -L < ip.txt)" '{printf "%-*s | %%%s\n", f, $0, NR}' ip.txt
c4-1 d e c            | %1
c d e c               | %2
e-2 f g2              | %3
e4 f g2               | %4
g8-4\( a-5 g f\) e4 c | %5
g'8\( a g f\) e4 c    | %6
c-1 r c2              | %7
c4 r c2               | %8
9
Sundeep

プレーンbash:bashバージョン> = 4.0で動作します

#!/bin/bash
mapfile -t lines < file
max=0
for line in "${lines[@]}"; do
    max=$(( ${#line} > max ? ${#line} : max ))
done
for i in "${!lines[@]}"; do
    printf "%-*s | %%%d\n" $max "${lines[i]}" $((i+1))
done

古いbashバージョンの場合、mapfileをwhile-readループに置き換えます。これはバージョン3.2で機能します

#!/bin/bash
lines=()
max=0
while IFS= read -r line || [[ -n "line" ]]; do
    lines+=("$line")
    max=$(( ${#line} > max ? ${#line} : max ))
done < file
for i in "${!lines[@]}"; do
    printf "%-*s | %%%d\n" $max "${lines[i]}" $((i+1))
done
4
glenn jackman

記録のためだけです:(これはひどく遅いですが、wc -L
columnを使用して@Freddyの答えを確実に求めています!

#!/bin/bash

file="$1"

ll=$(wc -L < "$file")

while read -r line; do
    if [ -z "$line" ]; then
        echo
        continue
    fi
    sl=$(wc -L <<< "$line")
    if [ "$ll" = "$sl" ]; then
        as=$(echo "$ll - $sl" | bc)
    else
        as=$(echo "$ll - $sl + 1" | bc)
    fi
    space=$(printf '\ %.0s' $(seq "$as") )
    n=$((++n)) \
    && grep -vE "^$|^%" <<< "$line" \
    | sed "s/$/$space\ \|\ \%$(printf "%s" "$n")/"
done < "$file"

それは1つの追加スペースで動作していますが:

c4-1 d e c             | %1
c d e c                | %2
e-2 f g2               | %3
e4 f g2                | %4
g8-4\( a-5 g f\) e4 c  | %5
g'8\( a g f\) e4 c     | %6
c-1 r c2               | %7
c4 r c2                | %8
2
nath

データに@文字がないと仮定します(ここで使用されている2つの@をその場合は別の文字に置き換えてください)。

$ awk -v OFS='@| %' '{ print $0, FNR }' file | column -s '@' -t
c4-1 d e c             | %1
c d e c                | %2
e-2 f g2               | %3
e4 f g2                | %4
g8-4\( a-5 g f\) e4 c  | %5
g'8\( a g f\) e4 c     | %6
c-1 r c2               | %7
c4 r c2                | %8

これは、文字列@| %を出力フィールド区切り文字として使用し、入力の後に各行の行番号(この区切り文字で区切られます)を出力し、次にcolumnを使用してこれを@文字に揃えます(これらは削除されます) 。


sedまたはぎこちない正規表現が好きな場合は、常にcat -nまたはnl -b aを使用して行に番号を付け、その行の末尾に行番号を移動し、sedを使用して@| %を挿入できます。 、columnを呼び出す前に:

$ cat -n file | sed -E 's/^[[:blank:]]*([[:digit:]]+)[[:blank:]]*(.*)$/\2@| \%\1/' | column -s '@' -t
c4-1 d e c             | %1
c d e c                | %2
e-2 f g2               | %3
e4 f g2                | %4
g8-4\( a-5 g f\) e4 c  | %5
g'8\( a g f\) e4 c     | %6
c-1 r c2               | %7
c4 r c2                | %8

awkを使用してファイルを2回読み取り、1回目は行の最大長(m)を計算し、もう1回は行をこの長さにフォーマットします。 columnはここでは使用しません(または最後のソリューションでは使用しません):

$ awk 'FNR==NR { m=(length>m?length:m); next } { printf("%-*s | %%%d\n", m, $0, FNR) }' file file
c4-1 d e c            | %1
c d e c               | %2
e-2 f g2              | %3
e4 f g2               | %4
g8-4\( a-5 g f\) e4 c | %5
g'8\( a g f\) e4 c    | %6
c-1 r c2              | %7
c4 r c2               | %8

コマンドラインでファイル名が2回指定されていることに注意してください。


上記と同じですが、ファイルをメモリに配列(a)として保存し、最後にある最長の行の長さに従って印刷します。ディスクアクセスは、メモリ消費量の減少に役立ちます。

$ awk '{ a[FNR]=$0; m=(length>m?length:m) } END { for (i=1; i<=FNR; ++i) printf("%-*s | %%%d\n", m, a[i], i) }' file
c4-1 d e c            | %1
c d e c               | %2
e-2 f g2              | %3
e4 f g2               | %4
g8-4\( a-5 g f\) e4 c | %5
g'8\( a g f\) e4 c    | %6
c-1 r c2              | %7
c4 r c2               | %8
2
Kusalananda