-1

Take the following example:

ls -l | grep -i readme | ./myscript.sh

What I am trying to do is get ls -l | grep -i readme as a string variable in myscript.sh. So essentially I am trying to get the whole command before the last pipe to use inside myscript.sh.

Is this possible?

Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
securisec
  • 3,435
  • 6
  • 36
  • 63
  • 3
    No, it's not possible. All you get is a FIFO; you don't have any way to know what's on the other side of it. (Also, this has already been asked; give me a minute to try to find the duplicate...) – Charles Duffy May 27 '19 at 03:37
  • As one way to better reason about whether it can be reasonably expected to be possible, keep in mind that nothing about pipelines is specific to shells, or requires there to *be* a command line for the input at all. If you had a C program that did the same set of `mkfifo()`s, `dup2()`s and `execve()`s that a shell does to set up a pipeline, you'd have the same set of programs connected in the same way, but with no single string describing that having ever been constructed. – Charles Duffy May 27 '19 at 03:39
  • (At the OS level on UNIX, programs are passed *arrays* of C strings; the single-string construction is internal to the shell, never seen by the called program). – Charles Duffy May 27 '19 at 03:41
  • @vintnes, how does storing their output in a file give the program that's processing the file the text of the command that created the file? Because that's what they're asking for here -- being able to access the text of the command creating their input. – Charles Duffy May 27 '19 at 03:45
  • @CharlesDuffy XY Problem, then. Noone should ever be in this position. – vintnes May 27 '19 at 03:54
  • @securisec What problem would this variable solve for you? – vintnes May 27 '19 at 03:55
  • @vintnes, an XY problem is when they're asking for something different than what they actually want. This seems like a pretty direct question here to me; I don't see any evidence that they have a different "real problem". – Charles Duffy May 27 '19 at 03:56

4 Answers4

3

No, it's not possible.

At the OS level, pipelines are implemented with the mkfifo(), dup2(), fork() and execve() syscalls. This doesn't provide a way to tell a program what the commands connected to its stdin are. Indeed, there's not guaranteed to be a string representing a pipeline of programs being used to generate stdin at all, even if your stdin really is a FIFO connected to another program's stdout; it could be that that pipeline was generated by programs calling execve() and friends directly.

The best available workaround is to invert your process flow.

It's not what you asked for, but it's what you can get.

#!/usr/bin/env bash
printf -v cmd_str '%q ' "$@"  # generate a shell command representing our arguments

while IFS= read -r line; do
  printf 'Output from %s: %s\n' "$cmd_str" "$line"
done < <("$@")       # actually run those arguments as a command, and read from it

...and then have your script start the things it reads input from, rather than receiving them on stdin.

...thereafter, ./yourscript ls -l, or ./yourscript sh -c 'ls -l | grep -i readme'. (Of course, never use this except as an example; see ParsingLs).

Charles Duffy
  • 280,126
  • 43
  • 390
  • 441
  • This is awesome! But, i actually already have the parameterized option implemented using getopt. What i was trying to see if i could do is reverse the order of it. https://pastebin.com/hzbmvk5K this is my full script as it sits. you will notice in line 99 the # todo is where i am trying to capture the piped output in my `command` variable for my post request. – securisec May 27 '19 at 04:00
  • That link is showing as "not available" when I try to follow it. – Charles Duffy May 27 '19 at 15:46
  • ...that said, if you're just trying to capture piped content, `content=$(cat)` is all you need to read all of your stdin into a single variable, and this *really was* an XY problem. – Charles Duffy May 27 '19 at 15:49
  • No, i think your initial assessment is correct. I am trying to capture the command that is being piped to the script, not the output of the commands that is being piped. To get the output, i am using read. https://pastebin.com/qUBzkxii thats the new link to the whole script. Note line #99 – securisec May 28 '19 at 15:30
  • BTW, pastebin is full of JavaScript-y, animated ads. Please consider using something like https://gist.github.com/ or http://ix.io/ going forward. – Charles Duffy May 28 '19 at 15:35
  • Ah, i didnt consider gist. Thanks for the suggestion. But yes, i think now you can see what i am trying to accomplish, and what I presently have. Apologies in advance as BASH really isnt my strong suite. – securisec May 28 '19 at 15:47
  • (At work right now, on a company-monitored network, so haven't actually had a chance to look at the code yet; if the link is still good this evening, I'll try to take a look then). – Charles Duffy May 28 '19 at 16:16
2

It can't be done generally, but using the history command in bash it can maybe sort of be done, provided certain conditions are met:

  • history has to be turned on.

  • Only one shell has been running, or accepting new commands, (or failing that, running myscript.sh), since the start of myscript.sh.

  • Since command lines with leading spaces are, by default, not saved to the history, the invoking command for myscript.sh must have no leading spaces; or that default must be changed -- see Get bash history to remember only the commands run with space prefixed.

  • The invoking command needs to end with a &, because without it the new command line wouldn't be added to the history until after myscript.sh was completed.

The script needs to be a bash script, (it won't work with /bin/dash), and the calling shell needs a little prep work. Sometime before the script is run first do:

shopt -s histappend
PROMPT_COMMAND="history -a; history -n"

...this makes the bash history heritable. (Code swiped from unutbu's answer to a related question.)

Then myscript.sh might go:

#!/bin/bash
history -w
printf 'calling command was: %s\n' \
    "$(history | rev | 
       grep "$0" ~/.bash_history | tail -1)"

Test run:

echo googa | ./myscript.sh &

Output, (minus the "&" associated cruft):

calling command was: echo googa | ./myscript.sh &

The cruft can be halved by changing "&" to "& fg", but the resulting output won't include the "fg" suffix.

agc
  • 7,973
  • 2
  • 29
  • 50
  • 1
    This is, obviously, very fragile. If you have several shells running at once, you need to hope that you're lucky enough that the right one gets its history flushed at the right time. You also need to hope that the command wasn't invoked in a way that prevents it from being shown in history (with some options, f/e, prepending a space will do that), and that the user hasn't `unset HISTFILE` (which I often do, when I'm running content that isn't worth saving). – Charles Duffy May 28 '19 at 00:45
  • @CharlesDuffy, Thanks. See revised answer. On `history` being flushed, it seems to do that anyway, but perhaps leading the script with `history -w` couldn't hurt... – agc May 28 '19 at 10:01
  • *nod*. Regardless of my warnings that this isn't a practice people should try to *actually do*, it _is_ a working answer (subject to substantial but fully-disclosed caveats), and having it here is a useful addition. – Charles Duffy May 28 '19 at 11:43
0

I think you should pass it as one string parameter like this

./myscript.sh "$(ls -l | grep -i readme)"
Hernan Garcia
  • 1,416
  • 1
  • 13
  • 24
  • Thanks. But that is not what i am trying to accomplish. The output of the commands will be piped to `myscript.sh` so i need to preserve the syntax in the OP – securisec May 27 '19 at 03:37
  • did you try what I've shared? the `"$(command)"` will expand the expression and will convert the output of the command into an string. I think it's exactly what you're trying to achieve – Hernan Garcia May 27 '19 at 11:33
  • @HernanGarcia, the OP isn't asking for the *output* of the command, they're asking for the *actual command itself*. They already have the output (available by reading stdin). – Charles Duffy May 27 '19 at 15:47
-1

I think that it is possible, have a look at this example:

#!/bin/bash
result=""
while read line; do
  result=$result"${line}"
done
echo $result

Now run this script using a pipe, for example:

ls -l /etc | ./script.sh

I hope that will be helpful for you :)

El-MAHMOUDI
  • 185
  • 4
  • How does that example demonstrate the script having access to the string `ls -l /etc`, as opposed to the *output* of that command? – Charles Duffy May 27 '19 at 15:45
  • (BTW, you should always quote your expansions; `echo "$result"`, not `echo $result`; otherwise, if a name contains a `*` with spaces around it, f/e, it'll get replaced with a list of additional names). – Charles Duffy May 27 '19 at 15:45