I need to copy /root/nbu/file1.sh to every user's home directory if user's id is even number.

Trying to execute following script:

cat /etc/passwd | while read LINE
    username=$(awk -v var="$LINE" -F: '{
        if ($3 % 2 == 0)
           print $1
    cp /root/nbu/file1.sh ~"$username"

And it does nothing. If I echo cp /root/nbu/file1.sh ~"$username" command and execute it directly in the script, it works.

I guess the problem is that ~ gets expanded in the bash script, but can't figure out how to solve this problem.

Thank you in advance.


You seem on the ball so I won't spoon-feed you a script, but here are some pointers:

  1. You're using awk incorrectly. Try this instead:

    awk -F: -v uid_min=${UID_MIN:-1000} '$3%2==0 && $3>uid_min && $3!=65534{print $6}' /etc/passwd
  2. There's no need for cat.

  3. Use your awk output as the input to your read loop, ie. while read USER_HOME_DIR ; do ... ; done < $(awk...). Understand that this will mean that only one awk process needs to be spawned, while your original script spawns a separate awk process for each line, so this is much more efficient.

  4. Add a check in your awk program to limit it to UID's above 1000, or you will inadvertently perform your copy for many system users.

  5. Note that in #1 above, I changed your $1 to $6 in order to pull the user's home directory instead of the user's name.

  6. I just noticed on my debian machine that there exists a 'nobody' user with UID 65534, so you may need to account for that. I modified the awk statement in #1 accordingly.

  7. As per user Jeff Schaller's comment, I've modified the awk program to account for a custom minimum UID. The form ${UID_MIN:-1000} means set the value as 1000 if it would otherwise be null or unset.

  • 1
    The Q isn't tagged linux, but if that's the scope, you might use UID_MIN rather than hard-coding 1000 in case the local sysadmin adjusted it. – Jeff Schaller Mar 7 '18 at 18:12
  • @JeffSchaller - Thanks. incorporated into the answer. – user1404316 Mar 7 '18 at 18:32
  • I should have clarified -- UID_MIN isn't globally set; it's a parameter in /etc/login.defs; sorry! – Jeff Schaller Mar 7 '18 at 18:39
  • Well, at least it was an opportunity teach the shell idiom ${FOO:-default}. At this point, I'll leave it in for that reason, and won't further change the answer, on the basis that the person running the script or one-liner will be a sysadmin who will know what the UID_MIN value is. – user1404316 Mar 7 '18 at 18:46

The tilde expansion doesn't work if the username part is quoted:

$ echo ~root ~"root"
/root ~root

(and it happens before variable expansion anyway.)

But since you're already reading passwd in the shell, why not just do it the full way:

getent passwd | while IFS=: read -r user pass uid gid gecos home shell; do 
    if (( uid % 2 == 0 )); then
        echo "uid $uid of user '$user' is even and their home is '$home'";

If your system has getent, it's probably better to use it, rather than /etc/passwd directly.

On the other hand, since you don't really seem to need the username for anything, you could just have your awk script output the directory ($6) instead.


That's because tilde expansion happens before variable expansions:

The order of expansions is: brace expansion; tilde expansion, parameter and variable expansion, arithmetic expansion, and command substitution (done in a left-to-right fashion); word splitting; and filename expansion.

echoing the result looks correct, and works if you re-evaluate it, but for a script, you'll need to do it differently. Perhaps:

# ...
homedir=$(getent passwd "$username" | cut -d: -f6)
cp /root/nbu/file1.sh "$homedir"
# ...

I think I can do it in a one-liner. Check this out:

eval $(awk -v source="/root/nbu/file1.sh" -v uid_min=${UID_MIN:-1000} -F: '$3%2==0 && $3>uid_min && $3!=65534{printf "cp %s %s ;", source, $6 }' /etc/passwd)

For readability:

eval $( 
  awk \
    -v source="/root/nbu/file1.sh" \
    -v uid_min=${UID_MIN:-1000} \
    -F: '
      $3%2==0 && $3>uid_min && $3!=65534 {
         printf "cp %s %s ;", source, $6 
    ' /etc/passwd 
  • 1
    I would use source <( awk '...' ) instead of eval. – glenn jackman Mar 7 '18 at 19:17

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.