Home > Back-end >  How do I `tee` output to multiple piped commands with zsh on macOS?
How do I `tee` output to multiple piped commands with zsh on macOS?

Time:11-22

I am using the tmutil status command to give me the current TimeMachine backup status. It gives an output of the sort

% tmutil status
Backup session status:
{
    BackupPhase = Copying;
    ClientID = "com.apple.backupd";
    DateOfStateChange = "2022-11-21 11:23:03  0000";
    DestinationID = "89E08126-7132-4D14-80B4-EFD45E8C5BFC";
    FirstBackup = 1;
    Progress =     {
        Percent = "0.1640944884974286";
        TimeRemaining = 65013;
        "_raw_Percent" = "0.1640944884974286";
        "_raw_totalBytes" = 488603537408;
        bytes = 80177147549;
        files = 159679;
        totalBytes = 488603537408;
        totalFiles = 3345928;
    };
    Running = 1;
    Stopping = 0;
}

This is not JSON, though it looks a bit like it.

I want to report on the Percent Complete and the Time Remaining.

I can get the Percent Complete with

tmutil status | grep "raw_Percent" | LC_NUMERIC="C" awk -F '"' '{print "Percent Complete: " ($4 * 100) "%"} '

and I can get the Time Remaining with

tmutil status | grep "TimeRemaining" | awk '{print "Time Remaining: " ($3/3600) " hours"} '

How can I run tmutil once (it seems to be a little expensive), and send the output to two grep ... | awk ... commands?

My understanding is that I could do

tmutil status | tee > (grep "raw_Percent" | LC_NUMERIC="C" awk -F '"' '{print "Percent Complete: " ($4 * 100) "%"} ') | (grep "TimeRemaining" | awk '{print "Time Remaining: " ($3/3600) " hours"} ')

with each command (pipes and all) in brackets.

But, instead, I get

zsh: no matches found: (grep raw_Percent | LC_NUMERIC=C awk -F " {print "Percent Complete: " ($4 * 100) "%"} )

CodePudding user response:

You are trying to redirect the standard output of tee to a subshell. What you want are two separate process substitutions, tee >(...) >(...). (Note the lack of a space between > and ( in each case.)

As an aside, you don't need to use grep in either case; just let awk filter the lines.

tmutil status | 
  tee >(LC_NUMERIC="C" awk -F '"' '/raw_Percent/ {print "Percent Complete: " ($4 * 100) "%"} ') \
      >(awk '/TimeRemaining/ {print "Time Remaining: " ($3/3600) " hours"} ')

CodePudding user response:

After a bit of struggle, I figured out how to use plutil to convert the output of tmutil into JSON. (The trick is that the first line is just raw text and must be discarded, but the rest is the property-list format that plutil can parse.)

# Assuming the output shown in your question
% tmutil status | sed '1d' | plutil -convert -json -o - - | jq '.Progress| ._raw_Percent, .TimeRemaining'
0.1640944884974286
65013

This makes parsing more robust, and (with jq) provides simpler input to a single awk command, if you don't want to try your hand at producing the desired text in jq itself.

Though zsh can handle floating-point arithmetic itself, so you probably don't even need awk. For example,

% tmutil status | ... | { read pct; read tr; }
% print "Percent complete: $(( tr * 100 ))%"
Percent complete: 16.409448849742862%
  • Related