4

I created an alias in order not to write ls every time I move into a new directory:

alias cl='cd_(){ cd "$@" && ls; }; cd_'

Let us say I have a folder named "Downloads" (which of course I happen to have) so I just type the following in the terminal:

cl Downloads

Now I will find myself in the "Downloads" folder and receive a list of the stuff I have in the folder, like say: example.txt, hack.hs, picture.jpg,...

If I want to move to a directory and look if there is, say, hack.hs I could try something like this:

cl Downloads | grep hack

What I get is just the output:

hack.hs

But I will remain in the folder I was (which means I am not in Downloads).

I understand this happens because every command is executed in a subshell, and thus cd Downloads && ls is executed in a subshell of its own and then the output (namely the list of stuff I have) gets redirected via the pipe to grep. This is why I then am not in the new folder.

My question is the following:

How do I do it in order to be able to write something like "cl Downloads | grep hack" and get the "hack"-greped list of stuff AND be in the Downloads folder?

Thank you very much, Pol


For anyone ever googling this: A quick fix was proposed by @gniourf_gniourf :

cl Downloads > >(grep hack)

Some marked this question as a possible duplicate of Make bash alias that takes duplicates, but the fact that my bash alias already takes arguments shows that this is not the case. The problem at hand was about how to execute a command in the current shell while at the same time redirecting the output to another command.

Community
  • 1
  • 1
Pol
  • 77
  • 6
  • 1
    what search terms did you try? – Mekap Jun 28 '16 at 11:23
  • 6
    I'd argue that's bad design (why do you even want an alias at all?) and bad workflow. (I can't even understand why you'd even want to do this). But a quick fix is to use a process substitution instead of a pipe: `cl Downloads > >(grep hack)`. – gniourf_gniourf Jun 28 '16 at 11:34
  • @Mekap:I do not understand your question... Do you mean what search terms did I try on the internet in order to answer my question on my own? Or do you mean what search terms did I try to grep? It doesn't matter what command I write after the pipeline: cl Downloads | command will take the list of stuff in Downloads as the input for the command. – Pol Jun 28 '16 at 11:34
  • Would it be acceptable to just make your function take multiple arguments? It wouldn't extend generally to any arbitrary piped command, but could be made to work with any specific ones. So it would, say, `cd` to `$1` then `grep` for `$2` if it is given? – Eric Renouf Jun 28 '16 at 11:34
  • 1
    "To @gniourf_gniourf :Thank you! This works." – Pol Jun 28 '16 at 11:40
  • To @EricRenouf : It would not generally be acceptable since I could try to do a cd without arguments (for example in order to cd ~) or with multiple arguments (for folders with spaces in the name) – Pol Jun 28 '16 at 11:41
  • 4
    You don't need the alias; just name the function `cl`. – chepner Jun 28 '16 at 12:02
  • Possible duplicate of [Make bash alias that takes parameter?](http://stackoverflow.com/questions/7131670/make-bash-alias-that-takes-parameter) – l0b0 Jun 28 '16 at 12:07
  • 3
    I'd also forgo the function and just write `cd Downloads && ls hack.*` – chepner Jun 28 '16 at 12:09
  • Part of why this question rubs people the wrong way, by the way, might be that it runs afoul of http://mywiki.wooledge.org/ParsingLs -- we spend a lot of time trying to teach folks *not* to make programmatic use of `ls`; using practices even demonstratively that give a contrary example is unhelpful to that. – Charles Duffy Jun 28 '16 at 14:51

1 Answers1

2

As you're aware (and as is covered in BashFAQ #24), the reason

{ cd "$@" && ls; } | grep ...

...prevents the results of cd being visible in the outer shell is that no component of a pipeline is guaranteed by POSIX to be run in the outer shell. (Some shells, including ksh [out-of-the-box] and very modern bash with non-default options enabled, will occasionally or optionally run the last piece of a pipeline in the parent shell, but this can't portably be relied on).

A way to avoid this, that's applicable to all POSIX shells, is to direct output to a named pipe, thus avoiding setting up a pipeline:

mkfifo mypipe
grep ... <mypipe &
{ cd "$@" && ls; } >mypipe

In modern ksh and bash, there's a shorter syntax that will do this for you -- using /dev/fd entries instead of setting up a named pipe if the operating system provides that facility:

{ cd "$@" && ls; } > >(grep ...)

In this case, >(grep ...) is replaced with a filename that points to either a FIFO or a /dev/fd entry that, when written to by the process in question, redirects output to grep -- but without a pipeline.


By the way -- I really do hope your use of ls in this manner is as an example. The output of ls is not well-specified for the range of all possible filenames, so grepping it is innately unreliable. Consider using printf '%s\0' * to emit a NUL-delimited list of non-hidden names in a directory, if you really do want to build a streamed result; or using glob expressions to check for files matching a specific pattern (BashFAQ #4 covers a similar scenario); extglobs are available if you need something closer to full regex matching support than POSIX patterns support.

Charles Duffy
  • 280,126
  • 43
  • 390
  • 441