3

in a bash script I call an external script that gives me the status of a program, like

~/bin/status

which returns something like

program is running

or

program is halted

Now I want to use only the last word, which I get e.g. with

STATUS=$(~/bin/status)
echo ${STATUS##* }

which gives me 'running' or 'halted', so far so good.

What I wonder is, is there a way to do this without storing the output of the status-script in a variable? I mean something like

echo ${$(~/bin/status)##* }

or

echo ${(~/bin/status)##* }

This call returns an error message 'bad substitution'. I've already searched for a solution but didn't find any. Maybe here is some bash specialist who can answer this? And no, it is not really a problem to do this with a variable, I'm just curious :-).

2
  • You have to fork: echo $( ~/bin/status | sed 's/.* //g' ) Commented Oct 28 at 10:18
  • 1
    In a perfect world, ~/bin/status would rather (even additionally) return an exit code of 0 or non-0, so you could simply have your own script do if ~/bin/status; then …. You'd also get the actual exit code for free in the $? special variable. Commented Oct 28 at 10:25

6 Answers 6

5

The presented way:

status=$(~/bin/status)
echo "${status##* }"

is the good and best way to do it. Use it, it is great.

Notes: I typically use $tmp variable name for such things. Prefer lower case variables for script temporary variables, use upper case for exported variables. Use quotes to prevent filename expansion. Check your script with shellcheck.

Sign up to request clarification or add additional context in comments.

Comments

4

You’re correct, Bash’s parameter expansion only works on variables, so you can’t apply ${…##* } directly to a command substitution. ${$(command)##pattern} is not valid Bash syntax.

If you want to avoid a variable, you could use one of these alternatives:

~/bin/status | awk '{print $NF}'

or

set -- $(~/bin/status)
echo "${!#}"

Both extract the last field without needing to store the full output in a separate variable.

1 Comment

set is storing the output, into the positional variables (and that may well be inconvenient, if already in use!).
4

One simple way:

read _ _ var < <(~/bin/status)
echo $var

Process Substitution >(command ...) or <(...) is replaced by a temporary filename. Writing or reading that file causes bytes to get piped to the command inside. Often used in combination with file redirection: cmd1 2> >(cmd2).

See http://mywiki.wooledge.org/ProcessSubstitution http://mywiki.wooledge.org/BashFAQ/024

1 Comment

This works but does assume the command always outputs 3 words. read -ra would be a useful alternative for capturing the output as an array and then you could print the last element regardless of how many words were outputted.
3

printf has all the same tools as the C version.

printf "%.0s%.0s%s\n" $(~/bin/status)

This prints the first two strings received as zero-length.

$: tst(){ local x=( running halted ); echo program is ${x[RANDOM%2]}; }
program is running
$: tst
program is halted

$:  printf "%.0s%.0s%s\n" $(tst)
running

You could also just use a filter pipe, since $() runs another subshell anyway.

$: tst | awk '{print $3}'
running

$: tst | cut -d' ' -f 3
halted

$: tst | sed 's/^program is //'
halted

$: tst | grep -Eo 'running|halted'
running

And if you want it "pretty" you could add a little local syntactic sugar.

$: myStat() { tst | cut -d' ' -f 3; }
$: myStat
running

$: alias other='tst | cut -d" " -f 3'
$: other
running

No variables.

Comments

3

is there a way to do this without storing the output of the status-script in a variable?

Probably not without being computationally more inefficient by piping the output into an additional process.

Also depends on your definitions of "variable" and "storing". (Are the positional parameters $@ variables? Or the standard input buffer to a program? Or fields or input records in awk? Or the pattern and hold spaces in sed? etc).


To add to the other answers:

: $(~/bin/status); echo "$_"

Bash defines _ as:

($_, an underscore.) This has a number of meanings depending on context. At shell startup, $_ set to the pathname used to invoke the shell or shell script being executed as passed in the environment or argument list. Subsequently, it expands to the last argument to the previous simple command executed in the foreground, after expansion. It is also set to the full pathname used to invoke each command executed and placed in the environment exported to that command. When checking mail, $_ expands to the name of the mail file.

(Emphasis mine.)

1 Comment

beware of the after expansion - both this and the code in the question will do pathname expansion. consider : $(echo a b '/b?n'); echo $_
1

Just pipe it through awk, like this:

~/bin/status | awk '{print $NF}'

Test with echo:

$ echo 'program is running' | awk '{print $NF}'
running

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.