1

I have the following bash script which takes the tabular data as input, get the first line and spit them vertically:

#!/bin/bash  
# my_script.sh
export LC_ALL=C
file=$1
head -n1  $file |
tr "\t" "\n" |
awk '{print $1 " " NR-1}'

The problem is that I can only execute it this way:

$ myscript.sh some_tab_file.txt 

What I want to do is on top of the above capability also allows you to do this:

$ cat some_tab_file.txt myscript.sh | myscript.sh 

Namely take it from pipe output. How can I achieve that?

2 Answers 2

5

I'd normally write:

export LC_ALL=C
head -n1 "$@" |
tr "\t" "\n" |
awk '{print $1 " " NR-1}'

This works with any number of arguments, or none if there are none. Using "$@" is important in this and many other contexts. See the Bash manual on special parameters and shell parameter expansion for more information on the many and varied notations available for controlling how shell parameters are handled. Generally, double quotes are a good idea, especially if the file names may contain spaces.

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

5 Comments

Note that this works because the head command reads from stdin if no file argument is given. If head didn't have this feature, the script would have to handle the "no arguments" case specially (as in @tripleee's answer).
Many Unix commands have the property of reading from standard input if no file name is given on the command line. There are not many commands that demand a - to read from standard input; when they do, it is usually because they require two files and use - to indicate one (e.g. diff). Many commands that accept zero or more file names also accept - as standard input. If the command does not read from standard input, you have to work harder; what's appropriate depends on what the command does. There's often "${@:-/dev/stdin}" if it requires a file name; that will work when - won't.
This particular command sequence will produce the wrong result if there is more than one input file, though. head prints a header when it receives multiple input files. The intent is apparently to only process the first line of the input, regardless of the number of input files.
Well, if that's what's wanted, the original code suffices; if there is no $1, then $file will be empty, and hence head will work on standard input. Alternatively, the code could validate the number of arguments and complain if there's more than one. If there could be spaces in the file name, then you have to work a tad harder: ${1:-"$1"} could be the argument to head. There are a quite a number of ways around any issues.
Oops: the notation should be ${1:+"$1"}, with + in place of -.
2

A common idiom is to fall back to the input file - if there are no parameters. There is a convenient shorthand for that;

file=${1--}

The substitution ${variable-fallback} evaluates to the variable's value, or fallback if it's unset.

I believe your script should work as-is, though; head will read standard input if the (unquoted!) file name you pass in evaluates to the empty string.

Take care to properly double-quote all interpolations of "$file", by the way; otherwise, your script won't work on filenames containing spaces or shell metacharacters. (Then you break the fortunate side effect of not passing a filename to head if your script did not receive one, though.)

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.