How do I properly quote this bash pipeline for watch?
The problem is about the single quotes for awk, you could fix it by escaping single quotes.
watch 'echo "scale=2;$(cat io | grep wchar | awk '"'"'{print $2}'"'"')/(1024^3)" | bc'
It is all about how to escaping single quotes inside single quotes, there is a good explanation "BASH, escaping single-quotes inside of single-quoted strings"
The watch command does not work with a command using a pipe
If you want to pipe a command and watch that, add quotes around it as such:
watch "ls | wc -l"
Add aliases for a watch command having pipe
alias fdbtop='watch -tc '\''fdbcli --exec "status json" | python /home/ubuntu/build-target/healthservice/fdbtop.py'\'
Within a string in single quotes no character is special. To insert a single quote in a string in single quotes you must use '\''
. (The first '
terminates the string, \'
adds a single quote, the last '
opens the string again.)
Actively display elapsed time using watch in bash
This is a basic script doing what you want, I presume:
#!/bin/bash
convertsecs() { #Function used to convert seconds into HH:MM:SS
((h=${1}/3600))
((m=(${1}%3600)/60))
((s=${1}%60))
printf "%02d:%02d:%02d\n" $h $m $s
}
typeset -fx convertsecs # export to sub-shells
# maybe this is implicit with your version of Bash
NOW=$(exec date +%s)
cmd() {
echo it"'"s fact of life than shell # Replace by *your*
echo quoting can be a '"nightmare"' # actual commands
}
typeset -fx cmd
watch -n 1 \
'bash -c '"'"'cat <(cmd) \
<(echo Elapsed $(convertsecs $(($(exec date +%s) - '$NOW'))))'"'"
The key issue is to properly quote the command passed to watch. In addition, I've exported the function convertsecs
so it is available in sub-shells.
Notice how I wrap the "user commands" in a function. That way, I don't have to worry about proper quoting of those commands when passed as an argument to watch
.
Moreover, here I used cat
with process substitution to append the line "Elapsed ..." after the output of my main command. I made that choice to clearly separate the "useful stuff" (cmd
) from the "cosmetic stuff" (Elapsed ...
).
How can I make the watch command interpret vt100 sequences?
Edit:
More recent versions of watch
support color. You will need to use an extra level of quoting to preserve the quotes and escapes in the particular situation of the example in the question:
watch 'echo -e "\033[31mHello World\033[0m"'
From man watch
:
-c, --color
Interpret ANSI color sequences.
Previously:
From man watch
:
Non-printing characters are stripped from program output. Use "cat -v"
as part of the command pipeline if you want to see them.
But they don't get interpreted, so I don't think there's any way.
Why piping input to read only works when fed into while read ... construct?
How to do a loop against stdin and get result stored in a variable
Under bash (and other shell also), when you pipe something to another command via |
, you will implicitly create a fork, a subshell that is a child of current session. The subshell can't affect current session's environment.
So this:
TOTAL=0
printf "%s %s\n" 9 4 3 1 77 2 25 12 226 664 |
while read A B;do
((TOTAL+=A-B))
printf "%3d - %3d = %4d -> TOTAL= %4d\n" $A $B $[A-B] $TOTAL
done
echo final total: $TOTAL
won't give expected result! :
9 - 4 = 5 -> TOTAL= 5
3 - 1 = 2 -> TOTAL= 7
77 - 2 = 75 -> TOTAL= 82
25 - 12 = 13 -> TOTAL= 95
226 - 664 = -438 -> TOTAL= -343
echo final total: $TOTAL
final total: 0
Where computed TOTAL could'nt be reused in main script.
Inverting the fork
By using bash Process Substitution, Here Documents or Here Strings, you could inverse the fork:
Here strings
read A B <<<"first second"
echo $A
first
echo $B
second
Here Documents
while read A B;do
echo $A-$B
C=$A-$B
done << eodoc
first second
third fourth
eodoc
first-second
third-fourth
outside of the loop:
echo : $C
: third-fourth
Here Commands
TOTAL=0
while read A B;do
((TOTAL+=A-B))
printf "%3d - %3d = %4d -> TOTAL= %4d\n" $A $B $[A-B] $TOTAL
done < <(
printf "%s %s\n" 9 4 3 1 77 2 25 12 226 664
)
9 - 4 = 5 -> TOTAL= 5
3 - 1 = 2 -> TOTAL= 7
77 - 2 = 75 -> TOTAL= 82
25 - 12 = 13 -> TOTAL= 95
226 - 664 = -438 -> TOTAL= -343
# and finally out of loop:
echo $TOTAL
-343
Now you could use $TOTAL
in your main script.
Piping to a command list
But for working only against stdin, you may create a kind of script into the fork:
printf "%s %s\n" 9 4 3 1 77 2 25 12 226 664 | {
TOTAL=0
while read A B;do
((TOTAL+=A-B))
printf "%3d - %3d = %4d -> TOTAL= %4d\n" $A $B $[A-B] $TOTAL
done
echo "Out of the loop total:" $TOTAL
}
Will give:
9 - 4 = 5 -> TOTAL= 5
3 - 1 = 2 -> TOTAL= 7
77 - 2 = 75 -> TOTAL= 82
25 - 12 = 13 -> TOTAL= 95
226 - 664 = -438 -> TOTAL= -343
Out of the loop total: -343
Note: $TOTAL
could not be used in main script (after last right curly bracket }
).
Using lastpipe bash option
As @CharlesDuffy correctly pointed out, there is a bash option used to change this behaviour. But for this, we have to first disable job control:
shopt -s lastpipe # Set *lastpipe* option
set +m # Disabling job control
TOTAL=0
printf "%s %s\n" 9 4 3 1 77 2 25 12 226 664 |
while read A B;do
((TOTAL+=A-B))
printf "%3d - %3d = %4d -> TOTAL= %4d\n" $A $B $[A-B] $TOTAL
done
9 - 4 = 5 -> TOTAL= -338
3 - 1 = 2 -> TOTAL= -336
77 - 2 = 75 -> TOTAL= -261
25 - 12 = 13 -> TOTAL= -248
226 - 664 = -438 -> TOTAL= -686
echo final total: $TOTAL
-343
This will work, but I (personally) don't like this because this is not standard and won't help to make script readable. Also disabling job control seem expensive for accessing this behaviour.
Note: Job control is enabled by default only in interactive sessions. So set +m
is not required in normal scripts.
So forgotten set +m
in a script would create different behaviours if run in a console or if run in a script. This will not going to make this easy to understand or to debug...
Multiple 'find' for watchers at once - only executes first one
If your task is simply to make sure the downstream copies of the two (or 1 + number of themes) directories are always up to date, a cron
job which syncs them every n minutes might be the simplest and most robust solution. cron
has a per-minute granularity, though something like every five minutes may well be sufficient for your use case. rsync
by design does not copy files which are already up to date, so it should be reasonably quick to execute in the no-op case.
* * * * * bin/rsyncthemes
(This is once per minute; on Linux you can say */5
in the first field to run every 5 minutes, but this is not portable to all cron
dialects.) ... where rsyncthemes
contains something like
#!/bin/sh
# cron jobs always start in your home directory,
# but you want to put an absolute path here
# to run this in the directory where you want rsync to copy these files
cd somewhere
for dir in code/themes/one \
code/themes/other \
code/base
do
rsync -avh "$dir"/* ./
done
where obviously this needs to be marked executable (chmod a+x rsyncthemes
) and saved in the directory we specify in the cron
job (viz. bin
in your home directory).
If you are hellbent on using entr
to avoid running rsync
needlessly, though, I'm guessing you want something like
for dir in code/themes/one \
code/themes/other \
code/base
do
while true; do
find "$dir"/src -name '*.html' -o -name '*.ts' -o -name '*.scss' |
entr -d rsync -avh "$dir"/* ./
done & # <-- run each loop as a background job
done
In some more detail, we are running three while true
loops as background jobs, one for each of the directories enumerated in the for
loop. They are running in parallel with your interactive shell, and will stop running when you log out (unless you separately set them up to stay running with something like nohup
). The &
operator is what runs something in the background, and putting it after the while
loop's done
statement makes it apply to the entire loop.
I'm guessing you may need to use entr -n
in order for the jobs to run in the background.
The wildcard in the rsync
command gets expanded by the shell, so you might need to refactor this to not use a wildcard, or to evaluate the wildcard only after find
finishes, if you expect it to be possible for new files to appear in these directories occasionally. (Quoting the entire rsync
command and passing it with entr -s
should trivially accomplish this, if I'm reading the entr
man page correctly.)
Using a pipe to read a file, run script and write to the same file
cat $1 | ./words_num word | tee $1
Related Topics
Shell Script to Delete Files When Disk Is Full
Using $Origin to Specify the Interpreter in Elf Binaries Isn't Working
Limit Output of All Linux Commands
Curl Command Doesn't Work in Bash Script
Undefined Reference to 'Pthread_Init' When Using -Lpthread Flag:
Error When Installing Opencv on Ubuntu 14.04
Bash Shell: Cannot Use Variable $ as a Path to Run Tar
How to Check If "S" Permission Bit Is Set on Linux Shell? or Perl
Output File Not Created When Reading Sequences
Compling C++ Code Using Command Line
Find and Replace a Word with Another in All File Names of a Directory
Can't Find Unexpected Operator (Bash Error)
After Segfault: Is There a Way, to Check If Pointer Is Still Valid
How to Print Multiple Strings on New Line in the Assembly Language