Lubricy
Lubricy

Reputation: 47

displaying command output in stdout then save to file with transformation?

I have a long-running command which outputs periodically. to demonstrate let's assume it is:

function my_cmd()
{
for i in {1..9}; do
    echo -n $i
    for j in {1..$i}
        echo -n " "
    echo $i
    sleep 1
done
}

the output will be:

1 1
2  2
3   3
4    4
5     5
6      6
7       7
8        8
9         9

I want to display the command output meanwhile save it to a file at the same time. this can be done by my_cmd | tee -a res.txt.

Now I want to display the output to terminal as-is but save to file with a transformed flavor, say with sed "s/ //g".

so the res.txt becomes:

11
22
33
44
66
77
88
99

how can I do this transformation on-the-fly without waiting for command exits then read the file again?

Upvotes: 0

Views: 94

Answers (3)

ghoti
ghoti

Reputation: 46856

Note that in your original code, {1..$i} is an error because sequences can't contain variables. I've replaced it with seq. Also, you're missing a do and a done for the inner for loop.

At any rate, I would use process substitution.

#!/usr/bin/env bash

function my_cmd {
  for i in {1..9}; do
    printf '%d' "$i"
    for j in $(seq 1 $i); do
      printf ' '
    done
    printf '%d\n' "$j"
    sleep 1
  done
}

my_cmd | tee >(tr -d ' ' >> res.txt)

Process substitution usually causes bash to create an entry in /dev/fd which is fed to the command in question. The contents of the substitution run asynchronously, so it doesn't block the process sending data to it.

Note that the process substitution isn't a REAL file, so the -a option for tee is meaningless. If you really want to append to your output file, >> within the substitution is the way to go.

If you don't like process substitution, another option would be to redirect to alternate file descriptors. For example, instead of the last line in the script above, you could use:

exec 5>&1
my_cmd | tee /dev/fd/5 | tr -d ' ' > res.txt
exec 5>&-

This creates a file descriptor, /dev/fd/5, which redirects to your real stdout, the terminal. It then tells tee to write to this, allowing the normal stdout from tee to be processed by additional pipe elements before final redirection to your log file.

The method you choose is up to you. I find process substitution clearer.

Upvotes: 1

oliv
oliv

Reputation: 13249

Instead of double loop, I would use printf and its formatting capability %Xs to pad with blank characters.

Moreover I would use double printing (for stdout and your file) rather than using pipe and starting new processes.

So your function could look like this:

function my_cmd() {
  for i in {1..9}; do 
    printf "%s %${i}s\n" $i $i
    printf "%s%s\n" $i $i >> res.txt
  done
}

Upvotes: 0

CWLiu
CWLiu

Reputation: 4043

Something you need to modify in your function. And you may use tee in the for loop to print and write file at the same time. The following script may get the result you desire.

#!/bin/bash

filename="a.txt"
[ -f $filename ] && rm $filename

for i in {1..9}; do
    echo -n $i | tee -a $filename 
    for((j=1;j<=$i;j++)); do
        echo -n " "
    done
    echo $i | tee -a $filename
    sleep 1
done

Upvotes: 0

Related Questions