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.


4 Answers 4


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"
# ...

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, 2018 at 18:12
  • @JeffSchaller - Thanks. incorporated into the answer. Mar 7, 2018 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, 2018 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. Mar 7, 2018 at 18:46

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. Mar 7, 2018 at 19:17

You must log in to answer this question.

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