I have the following output from a pipeline.
Lorem dolor sit amet consectetur
Lorem ipsum dolor sit ,
Lorem dolor sit amet ,
Lorem dolor ipsum sit !
Edit
The number of columns is specified by me/the user, but unlikely to be more than 10. My biggest data set so far looks like this:
prev_command | wc -l
$ 23805483
There are north of 300,000 unique tokens if I remember correctly.
Fields are separated by a space. Fields may contain all sorts of special symbols (→怅ð), but no spaces or tabs. I don't necessarily know the number of columns.
For every column (field), I need sorted word counts: essentially sort | uniq -c | sort -nr. Desired output:
4 Lorem 3 dolor 2 sit 2 amet 2 ,
1 ipsum 1 dolor 2 sit 1 consectetur
1 ipsum 1 !
Ideally, value and key are delimited by a space, and each val-key pair by a tab. Since the lenghts are different, line 3 would actually be \s\t\s\t1\sipsum\t\s\t1\s!.
What I have so far is a shell script that kinda works, but there are several things I don't like about it. Here are the relevant lines.
#!/usr/bin/env sh
tmp="$(mktemp -d)"
trap 'rm -rf -- "$tmp"' EXIT
data="${1:-/dev/stdin}"
[ "$data" != "$1" ] && cat $data > $tmp/table && data="$tmp/table"
count() {
sort | uniq -c | sort -rn \
| awk -v x=" " '{ print $1 x $2 }'
}
# infer column number from first line
ncol=$(head -1 $data | wc -w)
# loop over columns in parallel and save each in temporary file
for i in $(seq $ncol); do
cut -f $i -d " " $data | count > $tmp/col$i &
done
wait
paste $tmp/col[0-9]*
- How can I directly take a data stream on stdin and still have the option to take a file argument without losing much speed? If I remove the step with the tempfile, and pipe the original pipeline into the script, I still get an output, but not the expected one. Not sure what's going on. I experimented with
while read -ra..., but didn't know what to do inside the loop. - Any way I can avoid the whole tempfile business? In some cases, the input may be too large to hold in memory. It's also kind of painful to get the output into the right format with paste since it is ragged. When I want tabs only, I currently pipe it into a
sed -e 's/\t\t/\t\t\t/g' -e 's/^\t/\t\t/g'. Bleargh. Same withuniq -cwhich has leading whitespace. - How can I speed this up? I do have a perl version of
count(), but it's essentially doing the same thing and not taking care of i/o.
I'd prefer a bash/awk solution over perl since I don't know much perl and would prefer a solution that I understand (and also minimize dependencies). But if someone knows a mind-boggingly fast perl approach, I'd gladly take it. :D
This task seems pretty straightforward to me, but I couldn't find anything that handles variable columns and piped input. Thanks in advance for any tips! :)