2

In bash, I want to start my program (gollum) and use a part of the stderr (the port number) as an argument for another program. I redirect stderr to stdout and use grep and sed to filter the output. I get the result in stdout:

gollum -p0 2>&1|  sed  -n -e "s/.*port=//p"

It returns '56343' and continue to run as gollum is a server.

However if I want to use this as an argument of another program (i.e. echo but I want to use it later to launch an internet navigator with the port number ) with xargs it does not work.

gollum -p0 2>&1|  sed  -n -e "s/.*port=//p" | xargs -n1  echo  

Nothing happen. Do you know why, or do you have another idea to do the same thing.

Thank you for your help.

Charles Duffy
  • 280,126
  • 43
  • 390
  • 441
Arnaud B.
  • 151
  • 1
  • 7
  • 1
    Is the problem that Bash waits for all the children in the pipeline to die, and `gollum` doesn't die (the One Ring keeps him alive)? – Jonathan Leffler Sep 29 '16 at 22:41
  • Actually it is because gollum does not die. I kill from elsewhere and it works. How can I make my second program works as soon as the port number is returned ? – Arnaud B. Sep 29 '16 at 22:44
  • 1
    If `gollum` doesn't have an option to run in 'daemon' mode, then `(gollum -p0 2>&1 &) | sed … | xargs …` assuming you must use `xargs`. The subshell for the `( … )` doesn't wait for `gollum` to die. You might still have an issue with `sed` not dying, though; maybe you'd use `sed -n -e '/port=/{s/.*port=//;p;q;}'` to stop after the first port line. I think you'd need both subterfuges; you need the process that runs `gollum` to die and the `sed` that analyzes the output to die too. – Jonathan Leffler Sep 29 '16 at 22:48
  • I don't see the use case for xargs here at all. – Charles Duffy Sep 29 '16 at 22:49
  • @CharlesDuffy: I'm not sure about `xargs` — the echo is, I assume, a dummy command for the purposes of asking a minimal question. But if the code needed to capture the port number in `$(…)` for assignment, you need the processes to exit, I believe. – Jonathan Leffler Sep 29 '16 at 22:51
  • @JonathanLeffler, sure, but there's no obligation to use a command substitution. The OP isn't doing so, after all, and neither does my answer. – Charles Duffy Sep 29 '16 at 22:51
  • He's using the port number in the `xargs` — the object of running `xargs` shown in the question is to do `echo 56343`. If the intent is simply to get the port number to the standard output of the script containing this code, then `xargs` is not needed. If the object is to get something to use the port number, then it has to be captured in some shape or form. But there's not much point in discussing this unless the context is clarified. – Jonathan Leffler Sep 29 '16 at 22:54
  • Thank you Jonathan it works ! I will now try to understand the sed command ;-) . Actually the echo is an example, the port number change each time, the full command is more complex with something like ... | xargs -n1 -I{} open http://localhost:{} – Arnaud B. Sep 29 '16 at 22:59
  • @Nonolerobot, ...with what you were given above, `gollum` will die the first time it tries to write to stdout or stderr after `sed` exited, unless it's behaving in a nondefault way. Are you sure that's behavior you want? I wrote my answer assuming you wanted it to keep running (so, for instance, there would actually be an active service for the web browser to hit). – Charles Duffy Sep 29 '16 at 23:03
  • Finally my command line is: (gollum -p0 2>&1 &)| sed -n -e '/port=/{s/.*port=//;p;q;}' | (sleep 2 && xargs -n1 -I{} open http://localhost:{}) and it does exactly what want. Starting as many servers as I want and after they are started ( 2 seconds) opening a client on each one. I am just not familiar with sed. – Arnaud B. Sep 29 '16 at 23:12
  • Is `gollum` your own program or someone else's program that you're using? If it's your own, you can fix it to make this easier. For example, to have it write its port to a file you specify (`-P port-no-file`, for example). If it is long-running in the background, maybe it should fork and the parent should die, leaving the child to run (that's a part of 'daemonizing' the process — search on SO for `daemon` to find out more) and writing most of its error output to a log file; it could write just the port number to stdout). If the program is from a third party, you're stuck with what it does. – Jonathan Leffler Sep 29 '16 at 23:14
  • Thank you, I thought about it but gollum is a third party software ( https://github.com/gollum/gollum ) I do not want to force people to use my home made version. Your previous solution is fine to me. – Arnaud B. Sep 29 '16 at 23:18
  • 1
    Great. Do check out the options available — you're unlikely to be the only person who's needed the port number, so there's probably a way to find out which port number is in use other than have to parse stderr. I note that there is a `--port=4567` option (that's the default port number); if you specify `-p4567` instead of `-p0`, it will probably start on that port. Then you can circumvent all the issues; you tell it what to do rather than trying to make it tell you what it is doing. – Jonathan Leffler Sep 29 '16 at 23:48
  • Yes I was doibg this, but now iI want many servers. Even if I check that a port is free ,it can be busy by the time I launch goĺlum. I prefer it give me a new working port. They did p0 option for that case. – Arnaud B. Sep 30 '16 at 08:21
  • 1
    BTW, if this is Linux, you more options for detecting which port an instance is using than reading its output -- for instance, you can look directly at its file descriptor list in procfs (or use a tool such as `lsof` that does that for you). That way you aren't quashing errors, or depending on the format of logs built for human consumption (which can change with new releases, or depending on the current language/localization/locale settings!). – Charles Duffy Sep 30 '16 at 14:41

1 Answers1

2

This is easy to do if you don't want your server to keep running in the background. If you do, one approach is to use process substitution:

#!/bin/bash
#      ^^^^- not /bin/sh; needed for >(...) syntax

gollum -p0 > >(
  while IFS= read -r line; do
    # start a web browser here, etc.
    [[ $line = *"port="* ]] && echo "${line##*port=}"
    # ...for instance, could also be:
    # open "http://localhost:${line##*port=}/"
  done
) 2>&1 &

...if you want to read only the first line containing port=, then break out of the loop in the handler, and handle the rest of stdin before it exits (this is as easy as using cat >/dev/null).

l'L'l
  • 44,951
  • 10
  • 95
  • 146
Charles Duffy
  • 280,126
  • 43
  • 390
  • 441
  • Thank you, it probably works but I was hoping something simpler. – Arnaud B. Sep 29 '16 at 23:00
  • 2
    There's a difference between "simple" in terms of easy-to-read, and "simple" in terms of having very well-defined behavior. Often, in bash, you can have one form of simplicity or the other but not both. – Charles Duffy Sep 29 '16 at 23:02