Using awk
awk works well for this:
rev=$(svn info | awk '/Revision:/{print $2}')
The code above uses command substitution, $(...)
, to capture the output of a command and assign it to a variable.
In the code above, awk reads the output of svn info
and waits for a line that contains the string Revision:
. When that line is found, it prints the second field on that line, which is be the revision number.
Using sed
It is also possible to do this using sed:
rev=$(svn info | sed -n '/Revision:/ s/.* //p')
With the -n
option, sed will only print when we explicitly ask it to. /Revision:/
selects lines that contain the string Revision:
. For those lines, a substitution command is performed that removes all characters up to the last blank on the line and then, due to the p
option, the line is printed.
Using shell
while read -r name value
do
[ "$name" = Revision: ] && var="$value"
done < <(svn info)
The above uses redirection from process substition, < <(...)
, to supply the output of svn info
to a while loop. For each iteration of the loop, the first word on the line is assigned to name
and the rest of the line is assigned to value
. If name
is Revision:
, then the shell variable var
is assigned to value
, which is the revision number.
Many, but not all, shells support process substitution. When, as above, redirection is combined with process substitution, the space between the two <
is essential.
As you suspect, the exact behaviour is shell-dependent, but a baseline level of functionality is specified by POSIX.
Command search and execution for the standard shell command language (which most shells implement a superset of) has a lot of cases, but we're only interested for the moment in the case where PATH
is used. In that case:
the command shall be searched for using the PATH environment variable as described in XBD Environment Variables
and
If the search is successful:
[...]
the shell executes the utility in a separate utility environment with actions equivalent to calling the execl()
function [...] with the path argument set to the pathname resulting from the search.
In the unsuccessful case, execution fails and an exit code of 127 is returned with an error message.
This behaviour is consistent with the execvp
function, in particular. All the exec*
functions accept the file name of a program to run, a sequence of arguments (which will be the argv
of the program), and perhaps a set of environment variables. For the versions using PATH
lookup, POSIX defines that:
The argument file is used to construct a pathname that identifies the new process image file [...] the path prefix for this file is obtained by a search of the directories passed as the environment variable PATH
The behaviour of PATH is defined elsewhere as:
This variable shall represent the sequence of path prefixes that certain functions and utilities apply in searching for an executable file known only by a filename. The prefixes shall be separated by a <colon> ( ':' ). When a non-zero-length prefix is applied to this filename, a <slash> shall be inserted between the prefix and the filename if the prefix did not end in . A zero-length prefix is a legacy feature that indicates the current working directory. It appears as two adjacent characters ( "::" ), as an initial <colon> preceding the rest of the list, or as a trailing <colon> following the rest of the list. A strictly conforming application shall use an actual pathname (such as .) to represent the current working directory in PATH. The list shall be searched from beginning to end, applying the filename to each prefix, until an executable file with the specified name and appropriate execution permissions is found. If the pathname being sought contains a <slash>, the search through the path prefixes shall not be performed. If the pathname begins with a <slash>, the specified path is resolved (see Pathname Resolution). If PATH is unset or is set to null, the path search is implementation-defined.
That's a bit dense, so a summary:
- If the program name has a
/
(slash, U+002F SOLIDUS) in it, treat it as a path in the usual fashion, and skip the rest of this process. For the shell, this case technically doesn't arise (because the shell rules will have dealt with it already).
- The value of
PATH
is split into pieces at each colon, and then each component processed from left to right. As a special (historical) case, an empty component of a non-empty variable is treated as .
(the current directory).
- For each component, the program name is appended to the end with a joining
/
and the existence of a file by that name is checked, and if one does exist then valid execute (+x) permissions are checked as well. If either of those checks fails, the process moves on to the next component. Otherwise, the command resolves to this path and the search is done.
- If you run out of components, the search fails.
- If there's nothing in
PATH
, or it doesn't exist, do whatever you want.
Real shells will have builtin commands, which are found before this lookup, and often aliases and functions as well. Those don't interact with PATH
. POSIX defines some behaviour around those, and your shell may have much more.
While it's possible to rely on exec*
to do most of this for you, the shell in practice may implement this lookup itself, notably for caching purposes, but the empty-cache behaviour should be similar. Shells have fairly wide latitude here and have subtly different behaviours in the corner cases.
As you found, Bash uses a hash table to remember the full paths of commands it's seen before, and that table can be accessed with the hash
function. The first time you run a command it searches, and when a result is found it gets added to the table so there's no need to bother looking the next time you try it.
In zsh, on the other hand, the full PATH
is generally searched when the shell starts. A lookup table is prepopulated with all discovered command names so that runtime lookups usually aren't necessary (unless a new command is added). You can notice that happening when you try to tab-complete a command that didn't exist before.
Very lightweight shells, like dash
, tend to delegate as much behaviour as possible to the system library and don't bother to remember past command paths.
Best Answer
If using GNU
mv
, you should rather do:With other
mv
s:You should never embed
{}
in thesh
code. That's a command injection vulnerability as the names of the files are interpreted as shell code (try with a file called`reboot`
for instance).Good point for quoting the command substitution, but because you used the archaic form (
`...`
as opposed to$(...)
), you'd need to escape the inner double quotes or it won't work insh
implementations based on the Bourne shell or AT&T ksh (where"`basename "foo bar"`"
would actually be treated as"`basename "
(with an unmatched`
which is accepted in those shells) concatenated withfoo
and thenbar"`"
).Also, when you do:
If
bar
actually existed and was a directory, that would actually be amv foo/bar bar/bar
.mv -t . foo/bar
ormv foo/bar .
don't have that issue.Now, to store those several arguments (
-exec
,sh
,-c
,exec mv "$@" .
,sh
,{}
,+
) into a variable, you'd need an array variable. Shells supporting arrays are(t)csh
,ksh
,bash
,zsh
,rc
,es
,yash
,fish
.And to be able to use that variable as just
$FLATTEN
(as opposed to"${FLATTEN[@]}"
in ksh/bash/yash or$FLATTEN:q
in(t)csh
), you'd need a shell with a sane array implementation:rc
,es
orfish
. Alsozsh
here as it happens none of those arguments is empty.In
rc
/es
/zsh
:In
fish
:Then you can use: