0

The loop is adding the variables instead of rewriting it. I'm at a lost as to why this is happening. If anyone can help me then thank you really much I'm new to bash and I been at this for a few hours now...

for (( i=1; i<=4; i++ ))
do
    echo User:$(tail -n $i /etc/passwd | cut --delimiter=":" --fields="1")
    echo
    echo
done

The output becomes

User:art101c40
User:art101c39 art101c40
User:art101c38 art101c39 art101c40
User:art101c37 art101c38 art101c39 art101c40

It should look like this

User: art101c40
User: art101c39
User: art101c38
User: art101c37
3
  • 2
    Tail grabs -n lines from the bottom of a file then the subshell $() concatenates it’s output. So the output is expected. Try describing what problem you are trying to solve so we can help you better. Commented Nov 4, 2019 at 1:27
  • Yes, agreed, I think if you examine /etc/passwd (less, vi, cat) you'll see that the last 4 lines contain art101c37...... Good luck. Commented Nov 4, 2019 at 1:31
  • I'm trying to make a loop where it lays out the user names User: name User: name But the loop I have here is giving me User: Name Name User: Name Name Name It keeps adding onto the variable and not just overwriting it. The only way I have found that works is using printf instead of echo. Commented Nov 4, 2019 at 1:37

2 Answers 2

3

tail -n gets $i lines from the end of the file, so on the first loop you get one line, on the second loop you get two lines, etc. What you want to do is get the last four lines and iterate through them, which you can do with a while read loop:

tail -n 4 /etc/passwd |
    cut -d: -f1 |
    while read -r user  # "-r" prevents backslashes causing problems
do
    echo "User: $user"
done

Or you could use AWK to replace cut and the while read loop:

tail -n 4 /etc/passwd |
    awk -F: '{print "User:", $1}'

(with improvements from Gordon Davisson)

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

4 Comments

You could also eliminate the cut, and use awk -F: '{print "User:", $1}' to print just the first field.
So then I guess using the for loop just won't work then ? I also love how you was able to flip them around from low number to higher. As I also have other things to add to it. lol And I'm not sure how to add onto your coding as their is a few other fields I need to grab.
@SnakZ You could do the for-loop with tail -n $i | head -n 1, but it's easier to do it this way, especially where you want them in ascending order.
If its not to hard for you to do could you show me how with the for loop ? Also I'm still confused why it did what it did. I used to know some php coding. And from my understanding of that it should have work. I guess I'm just hooked on trying to figure out why it didn't work. Also how would I go about adding other items to the loop ? If you don't mind
1

@wjandrea already explained how to do this; I'll try to explain why the original didn't work. I'll also illustrate an important debugging technique for things like this: if a complex process isn't doing what you want, take it apart and run the pieces individually, and see what they're doing.

In this case, the first iteration the loop works as you expect (it prints "User:art101c40"), so let's not worry about that. Let's look at the second iteration, where i=2. With that value substituted, it runs the command:

echo User:$(tail -n 2 /etc/passwd | cut --delimiter=":" --fields="1")

So, let's run through the pieces and see what they do. First, it runs tail -n 2 /etc/passwd, which will print the last two lines of /etc/passwd. Something vaguely like this:

art101c39:*:10139:99:Art 101 class 39:/home/art101c39:/bin/bash
art101c40:*:10140:99:Art 101 class 40:/home/art101c39:/bin/bash

Note that it prints the last two lines (that's what tail -n 2 does), so it's printing both art101c39 and art101c40. This is the root of the whole problem.

But let's continue tracing its behavior. That output gets piped through cut --delimiter=":" --fields="1", which prints:

art101c39
art101c40

That gets captured by $( ) to be used as arguments to echo. Each of the usernames is on a separate line, so they're separated by a newline, but the $( ) is not in double-quotes so it's subject to word splitting. Word splitting breaks it into "words" based on whitespace (normally spaces, tabs, and newlines), so each username just becomes a separate word. Essentially, it becomes "art101c39" and "art101c40". If either of these had any filename wildcard characters, the shell would then try to turn them into lists of matching files; but there are no wildcard characters, so this doesn't happen. These "words" are then added into the arguments for echo, giving:

echo User:art101c39 art101c40

echo receives two arguments, "User:art101c39" and "art101c40". As it always does, it sticks these together with a space between them and prints the result.

Similar things happen on the third and fourth iterations, with tail printing three and then four accounts, and all of those accounts passing through the rest of the processing.

You could fix this directly by adding head to the pipeline: tail -n $i /etc/passwd | head -n 1 | .... With this, tail will print the last $i lines, and head will eliminate all but the first of these (the $ith from the end of the file). But this approach tends to be inefficient and (IMO) klugy. It involves reading through the entire file to pick out one field from one line. For four lines (accounts), you need to read the entire file four times. If you're going to pick out two fields from each of those users, that'd mean reading the file eight times.

There's usually a simpler and better way to do it, but what that is depends on what the actual goal is. Generally, you'd run tail once, and then common ways to deal with that output include: having awk -F: do all the work; piping to a while IFS=: read -r user pw uid gid fullname homedir shell; do loop; using readarray -t to pull the lines into a shell array where you can work with them directly; etc.

1 Comment

That explain everything I thank you very much and also a big thank you to @wjandrea over there and the rest of you for helping with my OCD of trying to figure out why this didn't work.

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.