Print a file in multiple columns based on delimite

2020-04-18 03:35发布

This seems like a simple task, but using duckduckgo I wasn't able to find a way to properly do what I'm trying to.

The main question is: How do I split the output of a command in linux or bash into multiple columns using a delimeter?

I have a file that looks like this: (this is just a simplified example)

-----------------------------------
Some data
that varies in line length
-----------------------------------

-----------------------------------
More data that is seperated
by a new line and dashes
-----------------------------------

And so on. Everytime data gets written to the file, it's enclosed in a line of dashes, seperated by an empty line from the last block. Line-length of the data varies. What I want is basically a tool or way using bash to split the file into multiple columns like this:

-----------------------------------        -----------------------------------
Some data                                  More data that is seperated
that varies in line length                 by a new line and dashes
-----------------------------------        -----------------------------------

Each column should take 50% of the screen, no centering (as in alignment) needed. The file has to be split per-block. Splitting the file in the middle or something like that won't work. I basically want block 1 go to the left column, block 2 to the right, 3 to the left again, 4 right, and so on. The file gets updated constantly and updates should be written to the screen right away. (Currently I'm using tail -f)

Since this sounds like a rather common question I would welcome a general approach to this instead of a specific answer that works only for my case so people coming from search engines looking for a way to have a two column layout in bash get some information too. I tried column and pr, both don't work as desired. (I elaborated on this in the comments)

Edit: To be clear, I am looking for a general approach on this. Going through a file, getting data between the delimiter, putting it to column A, getting the next one putting it to column B, and so on.

4条回答
相关推荐>>
2楼-- · 2020-04-18 04:04

Assuming file contains uniform blocks of five lines each, using paste, sed, and printf:

c=$((COLUMNS/2)) 
paste -d'#' <(sed -n 'p;n;p;n;p;n;p;n;p;n;n;n;n;n' file) \
            <(sed -n 'n;n;n;n;n;p;n;p;n;p;n;p;n;p' file) | 
sed 's/.*/"&"/;s/#/" "/' | 
xargs -L 1 printf "%-${c}s %-${c}s\n"

Problem with OP spec

The OP reports that the block lengths may vary, and should be separated by a fixed number of lines. Even numbered blocks go in Column A, odd numbered blocks in Column B.

That creates a tail -f problem then. Suppose the block lengths of the source input begin with 1000 lines, then one line, 1000, 1, 1000, 1, etc. So Column A gets all the 1000 line blocks, and Column B gets all the one line blocks. Let's say the blocks in the output are separated by 1 line each. So one block in Column A lines up with 500 blocks in Column B. So for a terminal with scrolling output, that means before we can output the first block in Column A, we have to wait for 1000 blocks of input. To output the third block in Column A, (just below the first block), we have to wait for 2000 blocks of input.

If the blocks are added to the input file relatively slowly, with a one second delay between blocks, then it will take three seconds for the block 3 to appear in the input file, but it will take 33 minutes for block 3 to be displayed in the output file.

查看更多
Fickle 薄情
3楼-- · 2020-04-18 04:06

This script is getting max width of current terminal and splitting it in 2, then printing records split by RS="\n\n" separator, print the first found and placing the cursor at the first line/last column of it to write the next record.

#!/bin/bash

tput clear
# get half current terminal width
twidth=$(($(tput cols)/2))

tail -n 100 -f test.txt | stdbuf -i0 -o0 gawk -v twidth=$twidth 'BEGIN{ RS="\n\n"; FS=OFS="\n"; oldNF=0 } {
    sep="-----------------------------------"
    pad="                    "
    printf "%-" twidth "s", $0

    getline

    for(i = 1; i <= NF; i++){
    # move cursor to first line, last column of previous record
    print "\033[" oldNF ";" twidth "f" $i
    oldNF+=1
    }
}'

Here's a simpler version

gawk 'BEGIN{ RS="[-]+\n\n"; FS="\n" } {
    sep="-----------------------------------"
    le=$2
    lo=$3
    getline

    printf "%-40s %-40s\n", sep,sep
    printf "%-40s %-40s\n", le,$2
    printf "%-40s %-40s\n", lo,$3
    printf "%-40s %-40s\n\n", sep,sep
}' test.txt

Output

-----------------------------------      -----------------------------------     
Some data                                More data that is seperated             
that varies in line length               by a new line and dashes                
-----------------------------------      -----------------------------------     

-----------------------------------      -----------------------------------     
Some data                                More data that is seperated             
that varies in line length               by a new line and dashes                
-----------------------------------      ----------------------------------- 
查看更多
做自己的国王
4楼-- · 2020-04-18 04:10

The question is tagged as Perl so here is a possible Perl answer:

#!/usr/bin/env perl
use strict;
use warnings;

my $is_col1 = 1;
my $in_block = 0;
my @col1;

while (<DATA>) {
    chomp;
    if (/^\s*-+\s*$/ ... /^\s*-+\s*$/) {
        $in_block = 1;
        if ($is_col1) {
            push @col1, $_;
        }
        else {
            printf "%-40s%-40s\n", shift @col1 // '', $_;
        }

    }
    else {
        if ($in_block) {
            $in_block = ! $in_block;
            $is_col1 = ! $is_col1;
            print "\n" if $is_col1; # line separating blocks
        }
    }
}

print join("\n", @col1), "\n\n" if @col1;

__DATA__
-----------------------------------
Some data
that varies in line length
-----------------------------------

-----------------------------------
More data that is seperated
by a new line and dashes
with a longer column2
-----------------------------------


-----------------------------------
The odd last column
-----------------------------------

Output:

-----------------------------------     -----------------------------------
Some data                               More data that is seperated
that varies in line length              by a new line and dashes
-----------------------------------     with a longer column2
                                        -----------------------------------

-----------------------------------
The odd last column
-----------------------------------
查看更多
走好不送
5楼-- · 2020-04-18 04:16

Alright, since apprently there is no clean way to do this I came up with my own solution. It's a bit messy and requires GNU screen to be installed, but it works. Any amount of lines within or around the blocks, 50% of the screen automatically resizing and each column prints independantly from each other with a fixed amount of newlines between them. Also automatic updates every x seconds. (120 in my example)

#!/bin/bash

screen -S testscr -X layout save default
screen -S testscr -X split -v
screen -S testscr -X screen tail -f /tmp/testscr1.txt
screen -S testscr -X focus
screen -S testscr -X screen tail -f /tmp/testscr2.txt

while : ; do
    echo "" > /tmp/testscr1.txt
    echo "" > /tmp/testscr2.txt
    cfile=1 # current column
    ctype=0 # start or end of block

    while read; do
        if [[ $REPLY == "------------------------------------------------------------" ]]; then
            if [[ $ctype -eq 0 ]]; then
                ctype=1
            else
                if [[ $cfile -eq 1 ]]; then
                    echo "${REPLY}" >> /tmp/testscr1.txt
                    echo "" >> /tmp/testscr1.txt
                    echo "" >> /tmp/testscr1.txt
                    cfile=2
                else
                    echo "${REPLY}" >> /tmp/testscr2.txt
                    echo "" >> /tmp/testscr2.txt
                    echo "" >> /tmp/testscr2.txt
                    cfile=1
                fi
                ctype=0
            fi
        fi
        if [[ $ctype -eq 1 ]]; then
            if [[ $cfile -eq 1 ]]; then
                echo "${REPLY}" >> /tmp/testscr1.txt
            else
                echo "${REPLY}" >> /tmp/testscr2.txt
            fi
        fi
    done < "$1"
    sleep 120
done

First, start a screen session with screen -S testscr then, either within or outside the session, execute the script above. This will split the screen vertically using 50% per column and execute tail -f on both columns, afterwards it will go through the input file and write block by block to each tmp. file in the desired way. Since it's in an infinite while loop it's essentially automatically updating the shown output every x seconds (here 120).

查看更多
登录 后发表回答