2

I'm trying to automate some tedious parts of a student job I'm working on.

Basically, the goal is to clone a bunch of git repositories (which I already have working), then run the same git checkout on all of them. Each time, I'm given something like this:

`git rev-list -n 1 --before="2016-04-29 23:59" master`

to feed to git checkout. I'd like to pass this entire thing into my script so that it can do the checkouts for me. Currently, my script is

#!/bin/bash

echo $1

(It was initially more complex, but I've since commented everything else out so I can focus on this input.)

When I try to run the script:

./RepoScan `git rev-list -n 1 --before="2016-04-29 23:59" master`

I get the following error:

fatal: Not a git repository (or any of the parent directories): .git

followed by a newline. I assume this means that git rev-list is trying to run, rather than being passed to RepoScan. Is there a way to package it so that it doesn't run when entered as an argument, but can still be run by the script?

(Also, I'm using OSX, if that matters.)

Trevor Muraro
  • 21
  • 1
  • 3
  • Using backticks as you are replaces the backticked string with the output of what is in the backticks, as a command. So if you run your script in a directory that is not a git repo, git rev-list will fail. – DopeGhoti May 08 '16 at 01:13

1 Answers1

2

Backquotes delimit a command substitution: the command inside the backquotes is executed, and its output is interpolated into the command line. (There are further complications if the backquotes are not inside double quotes, more on this later.) So what's happening is that git rev-list … is executed in the current directory, before RepoScan runs. It can't be otherwise since the output of the git command is passed as arguments to RepoScan.

If you want RepoScan to run a command that you specify when you invoke it, then you need to pass that command on the command line of RepoScan, not that command's output. There are two ways to do that, depending on what you mean by command.

Passing an executable with arguments

If the command is to be interpreted as an executable with some arguments, then pass the command as a list of words.

./RepoScan git rev-list -n 1 --before="2016-04-29 23:59" master

Inside the RepoScan script, use "$@" to refer to the command. The @ notation is a special variable that stands for the list of arguments. When used inside double quotes, it has a special behavior: each argument is put in a separate word, which is what makes "$@" equivalent to the list of arguments.

If the RepoScan command takes other options, they'll all have to be passed before the command. Use the shift builtin to remove processed options from the beginning of the command line. You would typically use the getopts builtin to parse options.

Passing a shell snippet

The other possibility is to pass a shell program snippet as a single argument.

./RepoScan 'git rev-list -n 1 --before="2016-04-29 23:59" master'

Note the two levels of quoting this time. The single quotes are for the shell in which you're calling RepoScan; everything inside single quotes is interpreted literally, so RepoScan gets one argument which is the string git rev-list -n 1 --before="2016-04-29 23:59" master. Inside RepoScan, since you have a shell snippet, execute it with

eval "$1"

if the snippet is in the first parameter, or

eval "$command"

if the snippet is in the variable command.

Note the double quotes around the variable expansion. This is necessary because $foo outside double quotes does not mean “the value of foo”, but “take the value of foo, split it according to IFS and interpret the result as a list of glob patterns” (a.k.a. the split+glob operator).