I have a script that repeats the same (very long) grep command many times. I want to make the script more readable by cutting the line length, so I tried to put parts of the command into variables.

This is the line (that repeats many times in the script, with different parameters):

rsync -av --delete -R --exclude=alternatives /etc/ /backup.raw/ | grep -E '^deleting|[^/]$' >> /var/log/rsync.log

That line works ok. What I did trying to abbreviate it, is:

_rscmd='rsync -av --delete -R'
_grep='grep -E "^deleting|[^/]$"'

so the "final" line could be:

$_rscmd --exclude=alternatives /etc/ $_mn | $_grep >> $_rslog

But that line fails on the $_grep variable. How can I write it right? I tried to use "…" and '…', with no luck. Pay attention to the original grep command that works, please.


The splitting into different parts (command & arguments) does not work if variables are used. Use eval for this case:

eval $_rscmd --exclude=alternatives /etc/ $_mn | eval $_grep >> "$_rslog"

In general, it is better to use shell functions or aliases than using variables:

alias my_grep='grep -E "^deleting|[^/]$"'
... | my_grep >> "$_rslog"
  • perfect man! eval it's the solution! thanks!. A note: the first variable doesn't need for the eval command, that works ok without it. Thanks again! – sergius Jul 3 '15 at 14:19
  • @sergius yes, you are right. bash does not require eval there, but as e.g. zsh would need it, I added it. – jofel Jul 3 '15 at 14:23

The usual way is to make only command's arguments as variable

_rscmdarg='-av --delete -R'
_greparg='-E "^deleting|[^/]$"'

as $_rslog is a file, and $_mn a dir, it is okay to keep them.

and the "final" line is:

rsync $_rscmdarg --exclude=alternatives /etc/ $_mn | grep  $_greparg >> $_rslog
  • I understand you, that's seems more clear. Thank you ! :) – sergius Jul 3 '15 at 14:29

A way to do this that includes (IMHO) the best parts of the other two answers is to write a shell function that embodies all the functionality that you want to be invariant, and takes arguments specifying the things that you want to be different on different instances:

my_rsync_grep() {
      rsync -av --delete -R "$1" "$2" "$3" | grep -E '^deleting|[^/]$' >> /var/log/rsync.log

and call it with the desired parameters:

my_rsync_grep --exclude=alternatives /etc/ /backup.raw/

This acts as if you had put the rsync … | grep … command into a separate script.  (Except it isn’t a separate file, so you don’t have the configuration management problem of keeping track of multiple files or the logistic problem of how to invoke another script (i.e., do you use ./ or not?), and you don’t have any extra disk I/O every time you invoke the function.)


  • As a matter of readability and programming style, you might want to move the I/O redirection (>> /var/log/rsync.log) out of the function and apply it to each invocation:

    my_rsync_grep --exclude=alternatives /etc/ /backup.raw/ >> /var/log/rsync.log

    This makes it obvious to anybody who reads or maintains the script that that line is writing to a file.  However, it also makes it easier to inadvertently introduce inconsistencies into the script.  Of course, you can (and probably should) put the filename into a variable (e.g., $_rslog), to reduce the risk of having one line that writes to /var/log/rsycn.log.

  • If you think that the function code is readability-impaired because it’s too long a line, you can break it into multiple lines.  Append a \ to each line but the last:

          rsync -av --delete -R "$1" "$2" "$3" | grep -E \
                  '^deleting|[^/]$' >> /var/log/rsync.log

    As a special case, if you break immediately after a |, &&, or ||, the \ is optional:

          rsync -av --delete -R "$1" "$2" "$3" |
                grep -E '^deleting|[^/]$' >> /var/log/rsync.log
  • The function, as written above, assumes that it will always be invoked with exactly three parameters.  This assumption seems unrealistic.  To run the rsync command with any/all parameters passed to the function, use "$@":

          rsync -av --delete -R "$@" | grep …
  • If, for example, you wanted to change the function to take the grep argument as an argument, e.g.,

    my_rsync_grep --exclude=alternatives /etc/ /backup.raw/ (other arg(s)) '^deleting|[^/]$'

    you can do it like this:

    my_rsync_grep() {
          rsync -av --delete -R "${@:1:nargs}" | grep -E "${@:$#}" >> /var/log/rsync.log

    See the Parameter Expansion section of bash(1) for more information on $@ and constructs like ${parameter:offset:length}.

    Note: This syntax might not work in shells other than bash.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.