0

Is there any way to check whether a command has a specific command line option from shell script?

My use case is tail command. In BSD version of tail, it has -r (reverse) option. This option is not available in GNU version

I can do a check from shell script to use this option only for MacOS, and use tac for Linux. But the problem is people can install GNU version of tail in MacOS too. So the better solution is to check whether the command has -r option.

oguz ismail
  • 1
  • 16
  • 47
  • 69
Phuong Nguyen
  • 2,960
  • 1
  • 16
  • 25
  • 1
    Use a function `myfunction() { case $(tail -r >/dev/null 2>&1) in 0) tail "$@";; *) tac "$@";; esac ; }` – Jetchisel Mar 30 '20 at 03:07
  • Or check the kernel? `case $(uname -s) *Darwin*|*BSD*) echo 'tail -r will work';; *Linux*) echo "tail -r will not work';; esac` – Jetchisel Mar 30 '20 at 03:17
  • @PhuongNguyen : You could do a `tail --version`, which will output the exact Gnu version if it is Gnu tail, but write an error message to stderr if it iMacOS tail. – user1934428 Mar 30 '20 at 06:10

3 Answers3

1

This is an impossible problem to solve in the general case; what happens if somebody has a different program installed as tail or an alias or shell function that changes its behaviour? You're finding out here that making portable/reliable shell scripts (particularly between very different operating systems) can be quite difficult. You'll have to decide for yourself how to make all these decisions and where you want to draw the line.

The quick & dirty way would be simply to call tail -r with some known input and see if the output matches what you'd expect. Use that to make a decision about what to do later on in your script. An example (probably not bulletproof):

#!/bin/bash

INPUT=$(mktemp)
OUTPUT=$(mktemp)
COMPARE=$(mktemp)

cat >${INPUT} <<EOF
1
2
3
EOF

cat >${COMPARE} <<EOF
3
2
1
EOF

tail -r ${INPUT} >${OUTPUT} 2>&1

cmp -s ${OUTPUT} ${COMPARE}
if [ $? == 0 ]
then
    echo "'tail -r' behaves as expected"
else
    echo "'tail -r' does not behave as expected"
fi

rm ${INPUT} ${OUTPUT} ${COMPARE}

It outputs as expected on a Mac and Linux machine I just tested.

Carl Norum
  • 219,201
  • 40
  • 422
  • 469
  • thanks, your answer is very close to what I'm doing in the end, so I accept this as the answer. What I do in the end is to call tail with a test input (using process substitution instead for shorter syntax) and compare that with the expected output. If the option is not available, it's going to give an error, which doesn't match the expected output: `if [ "$(tail -r <(echo '1') 2>&1)" = "1" ]; then echo '-r option is available' else echo 'not available' fi;` – Phuong Nguyen Mar 30 '20 at 08:30
  • Looks good, but be aware that your example doesn’t handle the case where the `-r` option exists but does something different than you expect. – Carl Norum Mar 30 '20 at 15:33
1

Neither tail -r nor tac is portable; you may come up with a solution that works in both BSD and GNU but that might fail on other systems. So, implement the same functionality in awk and use it instead. Like:

awk '{buf[NR]=$0} END{for(i=NR;i>0;i--)print buf[i]}'

This will work with all POSIX awks.

A conjunction of -r and -c or -n can also be emulated portably by piping tail to above command. -r and -b might be a bit challenging but is not impossible.

oguz ismail
  • 1
  • 16
  • 47
  • 69
  • 1
    thanks, your solution is also interesting, but I'm looking for a more general solution to apply to other similar problems too. – Phuong Nguyen Mar 30 '20 at 08:33
0

The common technique is to define the function if the utility found in the environment doesn't do what you want. Something similar to:

expect=$'2\n1'
if ! test "$(printf '1\n2\n' | tail -r)" = "$expect"; then
        tail() { if test "$1" = "-r"; then shift; command tail "$@" | tac;
                else command tail "$@"; fi; }
fi

In this particular case, the above function is not correct, as you would need to implement full option parsing to provide the required functionality (eg, tail -rn2 and tail -rn 2 need to be parsed and the positional parameters manipulated before passing them to tail). It would be easier to implement tac as a function when tac is missing.

William Pursell
  • 204,365
  • 48
  • 270
  • 300