9

How can I pipe grep output as an argument to the cd command?

For example:

[root@xxx xxx]# pip install django | grep '/usr.*'  
Requirement already satisfied (use --upgrade to upgrade): django in   /usr/lib64/python2.7/site-packages

Here /usr/lib64/python2.7/site-packages is highlighted and I want to pass this string to cd.

16

Use Bash's command substitution $(), you also need -o with grep to only select the matched portion:

cd "$(pip install django | grep -o '/usr.*')"

Note that although you will get away in this case but you should always enclose the command substitution with double quotes so that the shell does not perform word splitting on whitespaces (by default space, tab and newline, depends on the IFS variable in case of bash).

9

Depending on what you do and don't know beforehand about what pip will output, you might decide to grep for something other than /usr.*.

If you know the directory starts with /usr (and that it appears at the end of the line of output from pip, and that /usr does not appear anywhere on the line before the directory name), then that's a fine choice; heemayl's answer tells you how.

If the reason you know it starts with /usr is that you have just run the command and know the directory you want to change to, I suggest the simpler solution of running the command cd /usr/lib64/python2.7/site-packages. This is less typing even if you don't make use of tab completion.

Otherwise, you might choose a different regexp depending on what you know about the output being parsed. All the alternatives below still assume the directory name appears at the end of the line, but the other assumptions vary.

If you know the directory name is absolute (i.e., starts with a /) and no / appears on the line before the directory name, you can use the same regexp as in heemayl's answer but with / instead of /usr:

cd "$(pip install django | grep -o '/.*')"

This matches a / followed by zero or more (*) of any character (.).

If you know the directory name contains no horizontal whitespace (no spaces or tabs) and appears at the end of the line, you can use:

cd "$(pip install django | grep -oP '[^\h]+$')"

Here I've used a Perl regexp (-P) because the \h abbreviation (for [:blank:]) makes this easier to type and to read than an equivalent extended regexp (-E). This matches one or more (+) of any character in a class of characters ([ ]) that is not (^) a space or tab (\h).

If you know the directory name is immediately preceded by in surrounded by horizontal whitespace (i.e., padded on both the left and the right with blanks), and that this is the only such occurrence of in on the line, you can use:

cd "$(pip install django | grep -oP '\hin\h+\K.+')"

This uses a zero-width positive look-behind assertion (\K) to match one or more characters (.+) that appear after a space or tab (\h), in, and another one or more spaces or tabs (\h+), without actually including in and the blank spaces surrounding it in the match. Look-around assertions are a feature of Perl regular expressions.

The pattern \h+in\h+\K.+ would have worked also, but we only need to look for one blank before in, regardless of how many are present. In contrast, we must match all the blanks after in, or they wouldn't be discarded by \K and they would be matched as part of the directory name.

If you know the directory name is immediately preceded by the line's last occurrence of in followed by horizontal whitespace, you can use:

set +H
cd "$(pip install django | grep -oP '\hin\h+(?!.*\hin\h.*)\K.*')"
set -H

There, the zero-width positive look-behind assertion itself contains a zero-width negative look-ahead assertion ((?! )).

The ! appears in such a way as to be difficult to escape elegantly; the method I have used to prevent it from triggering shell history expansion before being passed to grep is to temporarily disable history expansion (set +H) before running the command and re-enable it (set -H) afterwards. If you're using this in a script and your script doesn't contain set -H, you don't need to do this as history expansion is enabled automatically only when a shell runs interactively.

Finally, note that none of these, nor heemayl's answer, are actually piping the output of grep to cd (though the output of pip is still being piped to grep). Rather than pipes, the appropriate tool for this job is command substitution.

  • Of course, if you want to get technical, the shell uses a pipe internally to read the command output. – Random832 Apr 24 '15 at 4:16
  • @Random832 Good point--I've edited slightly in consideration of this. Btw, do you know if the use of pipes "under the hood" in command substitution can be relied on? Or is it an implementation detail that might potentially be done differently in a different, future version of bash? (Of course, in practice, I understand it's the best way to do it, and thus it's unlikely to be implemented differently.) – Eliah Kagan Apr 24 '15 at 4:22
  • 1
    No, it's not guaranteed, in theory a shell could use a named fifo or temp file, though there's no reason to. – Random832 Apr 24 '15 at 11:28
  • Are you sure about the history expansion? The only characters that can block it are ` and '`, and you have use single quotes around the grep expression. – muru Apr 28 '15 at 2:16
  • 1
    @muru Yes, it's expanded. I hadn't noticed either, but found out when I tested it. The ! there isn't quoted with '-quotes, as the '-quotes are themselves quoted. The command's complexity makes it easy to mis-predict its effect, but apparently due to the order of expansions (! is early) it ends up working like x=foo; echo "'$x'" (--> 'foo'). Removing the outer "-quotes would cause ! to be '-quoted and prevent history expansion, but then cd would fail if the directory has whitespace in its name. – Eliah Kagan Apr 28 '15 at 3:00

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.