Reputation: 1716
I have a lot of background jobs running that are feeding their respective log files. Using the watch
command I am monitoring the respective last line in the log as follows.
watch "ls -rtdc1 base_*.log | tail -20 | xargs -I % sh -c 'tail -vn1 % | cut -c1-180'"
That looks nice, however it has the file names from tail -v
and following line from the log with a line break. I want to keep both on the same line. The following little awk cmd will just combine two lines and it tests out fine individually.
awk 'NR%2{printf "%s ",$0;next;}1'
Ok, now combining the two is the challenge. A lot of single and double quotes to watch out for. I tried the following line at it failed.
watch "ls -rtdc1 base_*.log | tail -20 | xargs -I % sh -c 'tail -vn1 % | awk \'NR%2{printf "%s ",$0;next;}1\' | cut -c1-180'"
So my question is to find the proper escaping sequence for the cmd line.
Your feedback is much appreciated.
Upvotes: 2
Views: 141
Reputation: 33685
Charles has a good answer for your specific problem. If you are asking the general problem of quoting, then GNU Parallel can do that for you:
$ parallel --shellquote
NR%2{printf "%s ",$0;next;}1
[Ctrl-D]
so awk NR%2\{printf\ \"%s\ \",\$0\;next\;\}1
should work. Next is to quote the command for sh -c
:
$ parallel --shellquote
tail -vn1 % | awk NR%2\{printf\ \"%s\ \",\$0\;next\;\}1 | cut -c1-180
[Ctrl-D]
Giving:
tail\ -vn1\ %\ \|\ awk\ NR%2\\\{printf\\\ \\\"%s\\\ \\\",\\\$0\\\;next\\\;\\\}1\ \|\ cut\ -c1-180
However, since you use % in the awk
command, you need to use another replacement string for xargs
. Let us use {}:
ls -rtdc1 base_*.log | tail -20 | xargs -I {} sh -c tail\ -vn1\ {}\ \|\ awk\ NR%2\\\{printf\\\ \\\"%s\\\ \\\",\\\$0\\\;next\\\;\\\}1\ \|\ cut\ -c1-180
Finally to quote it for watch
:
$ parallel --shellquote
ls -rtdc1 base_*.log | tail -20 | xargs -I {} sh -c tail\ -vn1\ {}\ \|\ awk\ NR%2\\\{printf\\\ \\\"%s\\\ \\\",\\\$0\\\;next\\\;\\\}1\ \|\ cut\ -c1-180
[Ctrl-D]
Giving:
ls\ -rtdc1\ base_\*.log\ \|\ tail\ -20\ \|\ xargs\ -I\ \{\}\ sh\ -c\ tail\\\ -vn1\\\ \{\}\\\ \\\|\\\ awk\\\ NR%2\\\\\\\{printf\\\\\\\ \\\\\\\"%s\\\\\\\ \\\\\\\",\\\\\\\$0\\\\\\\;next\\\\\\\;\\\\\\\}1\\\ \\\|\\\ cut\\\ -c1-180
This should therefore work:
watch ls\ -rtdc1\ base_\*.log\ \|\ tail\ -20\ \|\ xargs\ -I\ \{\}\ sh\ -c\ tail\\\ -vn1\\\ \{\}\\\ \\\|\\\ awk\\\ NR%2\\\\\\\{printf\\\\\\\ \\\\\\\"%s\\\\\\\ \\\\\\\",\\\\\\\$0\\\\\\\;next\\\\\\\;\\\\\\\}1\\\ \\\|\\\ cut\\\ -c1-180
Would you ever write it this way by hand? Probably not. But the whole idea is to make the computer work for you - not the other way around.
Upvotes: 1
Reputation: 295288
The Right Thing is to structure your code in such a way that you don't need to quote your code at all to make it be treated literally.
Consider the following (noting that everywhere I have a Function definition goes here placeholder below, it should be replaced with the myfn
definition given, or something equivalent thereto):
#!/usr/bin/env bash
# ^^^^-- needed for exported functions
myfn() {
# aside: parsing ls is a Really Bad Idea; don't do this.
# See https://mywiki.wooledge.org/ParsingLs for discussion of why not.
# See https://mywiki.wooledge.org/BashFAQ/003 for alternate practices.
ls -rtdc1 base_*.log \
| tail -20 \
| while read -r line; do
tail -v1 "$line" \
| awk 'NR%2 {printf "%s ",$0; next; }1' \
| cut -c1-180
done
}
export -f myfn
watch 'bash -c myfn'
Alternately, without the export -f
(and requiring bash only as the outer shell, not the inner one):
#!/usr/bin/env bash
# ^^^^- needed for declare -f
myfn() { echo "Function definition goes here"; }
watch "$(declare -f myfn); myfn"
Alternately, requiring only /bin/sh:
#!/bin/sh
cat_myfn() {
cat <<'EOF'
myfn() { echo "Function definition goes here"; }
EOF
}
watch "$(cat_myfn)
myfn"
Upvotes: 1