2

This is my assignment. The task is to output n longest lines from the input file(s). If there is no argument for n, the the default of value n is 5. If there is no files in the parameter, the standard input is used. If there are at least 2 files, output the name files. The output lines should be in the same order as in the original files. I've asked a question here to deal with the optional arguments: How to deal with optional input in shell script?

However, I'm stuck with another problem with the new shell script.

#!/bin/sh
while getopts “n” arg; do
  case $arg in
  n)
    # Check next positional parameter
    eval nextopt=\${$OPTIND}
    # existing or starting with dash?
    if [[ -n $nextopt && $nextopt != -* ]] ; then
      OPTIND=$((OPTIND + 1))
      level=$nextopt
    else
      level=5
    fi
    ;;
  esac
done

for name do
    if [ "$#" -gt 1 ]; then
        printf 'File: %s\n' "$name"
    fi
awk '{ print length(), NR, $0 | "sort -rn" }' $name | head -n $level | sed 's/[^ ]* //' | sort -n

done

I run it like this

sh ex1.sh -n 10 unix1.txt unix1.1.txt

and this is the output

File: -n
awk: can't open file -n
 source line number 1
File: 10
awk: can't open file 10
 source line number 1
File: unix1.txt
2 kbjkbkbbnbnmbnmnmmnbmnbmjbjkb
3 asjdsakdbakjsdbasbkj
4 asjdsakdbakjsdbasbkj
5 asjdsakdbakjsdbasbkj
10 ppûunsdj
11 tieutuvi
13 sdbhsdbjhdsvfdsvfgj
14 avavdvas
16 ffdsdfggdgdgdfgdfgdf112233
17 qwertyuiopsdfghjklxcvbnm,fghjk
File: unix1.1.txt
1 csdkbfsdk
2 fskjfnjkfnkjdsndjks
3 fsnjfnsjkf
4 snjfndsjknskjdfbnjksfdsfn
5 323124
6 jknjkkjnk4n4jn2
7 kjnjkb423
13 423b2j3kb4jk23bkb234kb32
14 234jb32jk43b
15 331

"-n" and "10" are not files. Also if I run like this

sh ex1.sh -n unix1.txt unix1.1.txt

The output should be 5 longest lines from the files, but instead:

File: -n
head: illegal line count -- unix1.txt
awk: can't open file -n
 source line number 1
File: unix1.txt
head: illegal line count -- unix1.txt
File: unix1.1.txt
head: illegal line count -- unix1.txt

So how can I fix this? Although this is not the goal, this would work

while getopts “n” arg; do
  case $arg in
  n)
    # Check next positional parameter
    eval nextopt=\${$OPTIND}
    # existing or starting with dash?
    if [[ -n $nextopt && $nextopt != -* ]] ; then
      OPTIND=$((OPTIND + 1))
      level=$nextopt
    else
      level=5
    fi
    ;;
  esac
done

awk '{ print length(), NR, $0 | "sort -rn" }' unix1.txt | head -n $level | sed 's/[^ ]* //' | sort -n

if I run

sh ex1.sh -n 

I've got

2 kbjkbkbbnbnmbnmnmmnbmnbmjbjkb
4 asjdsakdbakjsdbasbkj
5 asjdsakdbakjsdbasbkj
16 ffdsdfggdgdgdfgdfgdf112233
17 qwertyuiopsdfghjklxcvbnm,fghjk

or

sh ex1.sh -n 10

and I've got

2 kbjkbkbbnbnmbnmnmmnbmnbmjbjkb
3 asjdsakdbakjsdbasbkj
4 asjdsakdbakjsdbasbkj
5 asjdsakdbakjsdbasbkj
10 ppûunsdj
11 tieutuvi
13 sdbhsdbjhdsvfdsvfgj
14 avavdvas
16 ffdsdfggdgdgdfgdfgdf112233
17 qwertyuiopsdfghjklxcvbnm,fghjk

which are correct. Also, how to deal with 'If there is no files in the parameter, the standard input is used' ?

2 Answers 2

2

Your -n option takes an argument, so you need getopts 'n:' arg.

The option argument is found in $OPTARG.

Don't touch OPTIND in the while getopts loop.

After the loop, shift "$(( OPTIND - 1 ))". This leaves the filenames in the positional parameters.

That is,

#!/bin/sh

level=5
while getopts 'n:' arg; do
    case $arg in
      n) level=$OPTARG ;;
      *) echo 'Error in command line parsing' >&2
    esac
done

shift "$(( OPTIND - 1 ))"

for name do
    # stuff
done

Next, you never handle the case of no input files.

The following makes the script use standard input as a filename if there are none given:

if [ "$#" -eq 0 ]; then
    # handle no filenames, for example:
    set -- /dev/stdin
fi

for name do
     # stuff
done

I'll leave the rest to you (but I would strongly suggest moving the sort out of awk and instead run that as its own stage in the pipeline, for clarity).

4
  • 1
    The question does state "If there is no argument for n, the the default of value n is 5.", so you can't enforce that -n takes an argument. Aug 8, 2019 at 10:48
  • @glennjackman So the script may be called with -n and no argument? I read it as "if the number is not given with -n (i.e., the option and its argument are missing), it should default to 5". If you want to read it strictly, you don't know what to do if -n is not given at all.
    – Kusalananda
    Aug 8, 2019 at 11:11
  • @amV, Kusalananda is right. His code uses the default value when you don't provide the -n option, but if you use -n you must provide an argument. Aug 8, 2019 at 11:15
  • 1
    An optional option argument does not make sense. No standard tool behaves like that. The option may be optional, but if you use it, you must give an argument to it. Trying to do something else is just weird.
    – Kusalananda
    Aug 8, 2019 at 11:21
2

If there is no argument for n, the the default of value n is 5.

You might want to use getopt not getopts. It's not a bash builtin, so read its man page and look at the example code

Given this sample script

#!/usr/bin/env bash

tmp=$(getopt -o n:: --long n:: -- "$@")

if [[ $? -ne 0 ]]; then 
    echo "usage: ..." >&2
    exit 1
fi

eval set -- "$tmp"

n=0

while :; do
    case "$1" in
        -n|--n) n=${2:-5}; shift 2 ;;
        --)     shift; break ;;
        *)      echo "error" >&2; exit 1 ;;
    esac
done

echo "n = $n"
echo "remaining args:"
printf "%s\n" "$@"

The double colon indicates the option takes an optional argument. See what happens when it's called various ways:

$ bash sample.sh foo bar
n = 0
remaining args:
foo
bar
$ bash sample.sh -n foo bar
n = 5
remaining args:
foo
bar
$ bash sample.sh -n 10 foo bar
n = 5
remaining args:
10
foo
bar
$ bash sample.sh -n10 foo bar
n = 10
remaining args:
foo
bar
$ bash sample.sh --n foo bar
n = 5
remaining args:
foo
bar
$ bash sample.sh --n 10 foo bar
n = 5
remaining args:
10
foo
bar
$ bash sample.sh --n=10 foo bar
n = 10
remaining args:
foo
bar
$ bash sample.sh --n= foo bar
n = 5
remaining args:
foo
bar

Read the man page carefully:

A simple short option is a '-' followed by a short option character. If the option has a required argument, it may be written directly after the option char- acter or as the next parameter (i.e., separated by whitespace on the command line). If the option has an optional argument, it must be written directly after the option character if present.

A long option normally begins with '--' followed by the long option name. If the option has a required argument, it may be written directly after the long option name, separated by '=', or as the next argument (i.e., separated by whitespace on the command line). If the option has an optional argument, it must be written directly after the long option name, separated by '=', if present (if you add the '=' but nothing behind it, it is interpreted as if no argument was present; this is a slight bug, see the BUGS).

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .