0

My task is:

Write a command line that displays the output of a cat /etc/passwd, removing comments, one line every two starting with the second, inverting each login and sorting in reverse alphabetical order, keeping only the logins between FT_LINE1 and FT_LINE2 included, separated by "," (without quotes), and ending with ".".

This is my attempt so far:

cat /etc/passwd | grep ':*:' | cut -d : -f 1 | sed '1!n;d' | rev |
sort -rdf | sed -n "$FT_LINE1,$FT_LINE2" | sed 's:,: :g' |
awk 1 ORS=', ' | sed 's/..$/./' | tr -d '\n'

Using this code I get

sed: 1: ", p": invalid command code ,

What am I doing wrong?

Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
  • 1
    It appears that you've not set variables `$FT_LINE1` and `$FT_LINE2` appropriately. It seems that `$FT_LINE1` is unset and `$FT_LINE2` contains a blank and `p`. Since you've not shown us the rest of the code, it is hard to be sure. – Jonathan Leffler Jan 06 '21 at 17:37
  • 1
    `grep | cut | awk` is an anti-pattern. `grep | cut | sed | sed | sed | awk | sed | tr` is an anti-superhighway. – William Pursell Jan 06 '21 at 17:38
  • That is all the code. without p: sed: 1: ",": invalid command code , – Allef Sales Jan 06 '21 at 17:40
  • Note the [UUoC](https://stackoverflow.com/questions/11710552/useless-use-of-cat) — Useless Use of `cat`, though (as @WilliamPursell noted) it is hardly the worst offence in this sequence of commands. – Jonathan Leffler Jan 06 '21 at 17:40
  • Help me built a better code then. – Allef Sales Jan 06 '21 at 17:43
  • @AllefSales: what is the output from: `echo "FT_LINE1='$FT_LINE1'"; echo "FT_LINE2='$FT_LINE2'";`? Your problem is that those variables are not set appropriately. – Jonathan Leffler Jan 06 '21 at 17:44
  • ;both empty FT_LINE1='' FT_LINE2='' – Allef Sales Jan 06 '21 at 17:46
  • To help you build better code, we'd need to understand what you're trying to do. It is rather unobvious, to be polite about it. The first `grep` command selects all the lines in `/etc/passwd` that contain at least one colon — that's usually most of them, though on macOS the password file also has comment lines, some of which may contain colons. You're then looking for field 1 (`cut`). The first `sed` command might be an obscure way of writing `sed 1d`, or it might be something different. The `rev` command reverses the name, etc. – Jonathan Leffler Jan 06 '21 at 17:48
  • You say both `$FT_LINE1` and `$FT_LINE2` are empty. That is a major problem; they need to be set appropriately. I think there's a problem that something is missing in the question — if they're unset, that isn't the error message I get from `sed`. But you need to know what those variables should be set to. There is nothing in the question to give us even the remotest hint about what the values should be. – Jonathan Leffler Jan 06 '21 at 17:50
  • • Write a command line that displays the output of a cat / etc / passwd, removing comments, one line every two starting with the second, inverting each login and sorting in reverse alphabetical order, keeping only the logins between FT_LINE1 and FT_LINE2 included, separated by "," (without quotes), and ending with ".". – Allef Sales Jan 06 '21 at 17:52
  • That information should be in the question, not in a comment. I've transcribed it for you this time — please do it yourself in future. Since the task explicitly says `cat /etc/passwd`, you are excused the UUoC this time — teachers don't always follow best practices when setting exercises. You need to revise the `sed` command to `sed -n "${FT_LINE1},${FT_LINE2}p"` and set `FT_LINE1=3` and `FT_LINE2=9` or some such pair of numbers before running the command shown. Is that in a script file? Comment lines start with a `#` — don't go looking for colons because colons don't indicate 'non-comment'. – Jonathan Leffler Jan 06 '21 at 17:55
  • Is there a way to reset the variables? – Allef Sales Jan 06 '21 at 17:57

1 Answers1

0

As noted in the comments, the primary problem is that the shell variables $FT_LINE1 and $FT_LINE2 are not set correctly. You must ensure they are set to suitable values.

The exercise is convoluted and nasty. It also requires UUoC (Useless Use of cat), but you can't help that.

There are several ways to do it. A first variant is:

# Set range of lines to print
FT_LINE1=3
FT_LINE2=9
cat /etc/passwd |                       # UUoC required by question
grep -v '^[[:space:]]*#' |              # Non-comment lines
sed -e 's/:.*//' -e '1!n;d' |           # Remove info after login name; print lines 2,4,6,8,...
rev |                                   # Reverse each name
sort -rdf |                             # Sort in reverse dictionary order, case-insensitively
sed -n "${FT_LINE1},${FT_LINE2}p" |     # Select lines in the range $FT_LINE1 to $FT_LINE2 inclusive
tr '\n' ',' |                           # Convert newlines to commas
sed 's/,$/./'                           # Convert final comma to dot

This uses grep, sed, rev, sort and tr. The grep | sed part isn't entirely satisfactory, but line counting for lines 2, 4, 6, 8, … in sed gets messy if it also tries to eliminate comments.

A second variant uses awk twice. The first awk ignores comment lines and for every second non-comment line, reverses the data in field one before printing it (combining the grep | sed | rev sequence into one command). The second awk concatenates the lines from the sort into a comma-separated list and prints it with a dot at the end.

# Set range of lines to print
FT_LINE1=3
FT_LINE2=9
cat /etc/passwd |
awk -F: \
    '/^#/ { next }
     {  if (++numout % 2 == 0)
        {
            out = ""
            for (i = length($1); i > 0; i--)
                out = out substr($1, i, 1)
            print out
        }
     }
    ' |
sort -rdf |
awk -v line1="$FT_LINE1" -v line2="$FT_LINE2" \
    'NR >= line1 && NR <= line2 { out = out pad $1; pad = "," }
     END { print out "." }
    '

On my Mac, the output from the two scripts is the same — YMWV (Your Mileage Will Vary because your list of users will be different):

www_,vamalc_,toorsmvc_,toor,tocevod_,tnegaevitpac_,svc_.
www_,vamalc_,toorsmvc_,toor,tocevod_,tnegaevitpac_,svc_.

Given GNU Awk, it is probably possible to do the whole job in a single Awk script; it has built-in sorting functions. However, case-insensitive string comparison does not seem to be one of the built-in options, so it gets fiddly. Hence, I kept the external sort command. Where the first script has print out, a sorting script would use saved[i++] = out. The END block would then sort the saved array (probably using a custom function to sort case-insensitively), and then select elements from line1 through line2 of the sorted array, concatenating them and printing the result, rather like the second script does anyway.

Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278