3

For a half-finished script that already uses the output of a program I also need the name and the parameters of the program that was used to pipe to my script.

So I run it like this: yay something | ./myscript Now I need to store "yay something" into a variable.

There is a way to to get previous runned commands or the current one by using set -o history -o histexpand and echo !! or echo $0 but that doesn't include what I wrote right before the pipe.

Maybe you would suggest to pass the name of the program and it's parameter to my script as parameters and then run it there but I don't want this (pass a command as an argument to bash script).

Hans Müller
  • 33
  • 1
  • 7
  • Can't be done. These are completely separate processes; they don't know about each other, or have any means of communication other than the stdout of the former being directed to the stdin of the latter. That said, what's the context? It wouldn't be unusual to encapsulate the entire pipeline into a function. – Charles Duffy Sep 16 '18 at 21:54
  • ...if you showed us more about the larger goal you're trying to accomplish, we could describe what a conventional way to address the issue would be. BTW, note that `history` and friends aren't guaranteed to work in scripts or other noninteractive shells anyhow. – Charles Duffy Sep 16 '18 at 21:56
  • 2
    ...f/e, it would often be more appropriate (conventional, etc) to invoke `./myscript yay something`, and have `myscript` be responsible for redirecting its stdin from a command invoked using the arguments at hand. – Charles Duffy Sep 16 '18 at 21:57
  • ...if `myscript` had the line `exec < <(exec "$@")` just before the shebang, f/e, that would do the redirection I just described (repointing stdin to come from a program with arguments from its command line). – Charles Duffy Sep 16 '18 at 21:59
  • BTW, just to prove that in the general case what you're asking for is impossible, think about `yay something | ssh somehost ./myscript`. From the perspective of `./myscript`, `yay something` isn't even running on the same computer; it can't *possibly* determine where that stdin is from. You get the same issue in a bunch of other cases -- think about Python or C code where you've got `p1 = subprocess.Popen(['yay', 'something'], stdout=PIPE); p2 = subprocess.Popen(['./myscript'], stdin=p1.stdout)` -- now you have two commands piped together, but there *is* no shell that ever connected them. – Charles Duffy Sep 16 '18 at 22:01
  • Ok, your comments about processes gave me a idea how I may accomplish it with a workaround: in case that I would e.g. pgrep the process id of yay: Could I get it's parameters (in that case "something")? – Hans Müller Sep 16 '18 at 22:17
  • You can, but it's not a good practice. Assumes both processes are on the same machine, won't necessarily pick the right one if there's more than one instance running at a time, etc. – Charles Duffy Sep 16 '18 at 22:19
  • BTW, note that a process's parameters are passed as an array, not a string. Tools like `ps` generally are lossy in terms of how they convert that array to a string -- `./foo 'first argument' 'second argument'` is different from `./foo 'first' 'argument' 'second' 'argument'`, but you wouldn't know that by looking at most versions of `ps` or `top`. – Charles Duffy Sep 16 '18 at 22:21
  • ...if you *must* implement something like this (and again, I really advise that you don't), at least filter for sibling processes (that is to say, children of the same parent) to avoid false-positive matches. If you're only targeting Linux, you can read any process's parent out of procfs, and of course your own parent is `$PPID`. BTW, insofar as `ps`, `pgrep` and similar tools get their data from `/proc`, you can just access that content directly and thus avoid data loss caused by programs munging output to be human-readable rather than byte-for-byte accurate. – Charles Duffy Sep 16 '18 at 22:25
  • See https://stackoverflow.com/a/25319102/14122 re: how to invoke the pipe *from* the `yay` process, as more briefly discussed above. – Charles Duffy Sep 17 '18 at 11:58
  • Thanks a lot. processes=$(> >(ps -f)) and then a little bit filtering with grep did the job. – Hans Müller Sep 18 '18 at 05:32
  • If the implementation is somewhere on github, I'd be happy to audit it for failure cases you may not have thought of. (Alternately, feel free to add an answer to your own question). – Charles Duffy Sep 18 '18 at 13:33
  • ...actually, what would be a more *robust* solution would be to inspect your `/proc/self/fd/0`, and find the process writing to that same pipe. Assuming there's only one, of course, which isn't a reliable assumption; it's perfectly legal for more than one process to hold a copy of a FD. But you're prone to similar (for that matter, worse) confusion trying to inspect `ps` as well, if the programs on the write side of the FD fork, or pass their stdout FD over a domain socket, or otherwise do anything else that's remotely interesting. – Charles Duffy Sep 18 '18 at 13:44
  • https://raw.githubusercontent.com/schrmh/fakeAUR/b8f4cfac0c5a598f4bc559e928427477ca8c99d6/fakeAUR.sh – Hans Müller Sep 22 '18 at 06:09
  • The part where I get what is before the pipe works without problems. You don't need to audit this, I tested it a lot and I know the bugs that happen (most of them are output related e.g. when "yay something" gets more than one result. Also when I press the up key after executing "yay something | ./fakeAUR.sh" I get parts of the code instead of what I manually executed.). Also it's a script that I just made for fun and mostly for personal use. I even c&p'd a lot instead of creating a function. It may be more reliable to fake yay's complete output but I didn't want to deal with the formatting. – Hans Müller Sep 22 '18 at 06:25

1 Answers1

0

ALMOST PERFECT SOLUTION:

if [ -p /dev/stdin ]; then
    echo "You piped something to this script!"
    echo "Some output of the command you piped:"
    read input
    echo "$input"
    echo "That was all."
    pipe=true
else
    echo "No input piped to script."
fi
echo "NOTE: If you pipe the echo command into this script, you get wrong info"
echo "This script is a child of PID "$parent_pid": $(tr '\0' ' ' </proc/$parent_pid/cmdline)"
children="$(cat /proc/$parent_pid/task/$parent_pid/children)"
echo "All childs of this: $children"
echo "Check with this process table:"
ps -f
parent_tty="$(ps -o tty= -p $parent_pid)"
echo "tty of the parent should be: $parent_tty"
echo "Based on this, check this process tree:"
ps f | grep "$parent_tty"
wanted_pid=$(echo $children | cut -d' ' -f1)
echo "This is the cmdline for PID $wanted_pid ("$([ -z "$pipe" ] && echo "this script):" || echo "before the pipe | ):")
cmdline="$(tr '\0' ' ' </proc/$wanted_pid/cmdline)"
echo "$cmdline"
echo "Use this outside of a script (might lock your pacman db.lck, add killing code if so):" 
echo "$cmdline | tr '\0' ' ' </proc/\$(cat /proc/\$$/task/\$$/children | rev | cut -d' ' -f3 | rev)/cmdline"
echo ""
echo "I will kill $wanted_pid now. You might still see some output:"
sleep 0.1
kill -9 "$wanted_pid"
echo ""
echo "Script is done."

From the first revision which might be a bit easier to grasp: Thanks to Charles for his enormous effort and his link that finally led me to processes=$(> >(ps -f)). Here a working example. You can e.g. use it with vi test | ./testprocesses (or nano or package helpers like yay or trizen but it won't work with echo, man nor with cat):

#!/bin/bash -i

#get processes
processes=$(> >(ps -f))

echo beginning:
echo $processes

#filter
pac=$(echo $processes | grep -o -P '(?<=CM).*(?=testprocesses)' | grep -o -P '(?<=D).*(?=testprocesses)' | grep -o -P "(?<=00:00:00).*(?=$USER)")

#kill
delete=$(echo $pac | grep -oP "(?<=$USER\s)\w+")
pac=$(echo $pac | grep -o -P '(?<=00:00:00).*(?=)')
kill -9 $delete

#print
echo " "
echo end:
echo $pac

The kill part is necessary to kill the vi instance else it will still be running and eventually interfer with future executions of the script.

Hans Müller
  • 33
  • 1
  • 7
  • Consider running through http://shellcheck.net/ and addressing the issues it identifies -- mostly side effects to lack of quoting on the `echo` arguments. (To grok why that's important, see [I just assigned a variable but `echo $variable` shows something else!](https://stackoverflow.com/questions/29378566/i-just-assigned-a-variable-but-echo-variable-shows-something-else/52451742)) – Charles Duffy Sep 22 '18 at 14:39
  • Thanks for suggesting that. I was wondering how I could preserve that table view and it turns out that it gets preserved when "" are used.. Heh. Well, thanks to that I can add a way better solution. Edit: Updated the answer above. – Hans Müller Sep 22 '18 at 17:18
  • BTW, feel free to replace content outright when editing -- old revisions and the differences between them are still visible by clicking on the "edited [at time]" link. – Charles Duffy Sep 22 '18 at 17:55
  • Yeah, but the old one is working reliable that's why I keep it. I had a few updated versions and after some time I encountered some serious bugs with them and those are not worth keeping. The current version that I just updated now should work pretty reliable. It still does not work with echo, man nor cat though but I guess people who want that can figure it out. Maybe I will try it when I have time and post an update. – Hans Müller Sep 22 '18 at 18:14