As well as forgetting to add the username to the end of the useradd line, you're also making this far more complicated than it needs to be.
You don't need multiple array variables (unless you intend to do more with the CSV data after the accounts have been created - and even then, you're probably better off doing that inside the existing for
loop). You're also using bash
(which is a language that's terrible at text processing) instead of a language that's actually good at text processing (such as awk
or perl
).
Here's how to do what you want in a perl
one-liner:
$ perl -lne '($surname,$first,$username,$group,$secgroup,$comments) = split /\s*,\s*/;
system("echo","useradd","-g",$group,"-G",$secgroup,"-c",$comments,$username)' Newbies.csv
Example output:
useradd -g Accounting -G Emp -c 5326 aesche
useradd -g Accounting -G Emp -c 0699 kwest
useradd -g MArketing -G Emp -c 7208 mjuly
This is written to be a dry-run, only printing what it would do, without actually adding any user accounts. Remove the "echo"
from the system()
command once you've verified that it's generating the useradd
commands correctly.
Note that, unlike bash, quoting of variables isn't necessary when using perl's system()
function - each element of the list given to it is passed as an individual, separate argument to the program being executed by system (in this example, the program is echo
, or useradd
after you've deleted the "echo"
from the list), even if it contains whitespace characters. Unless you pass it wildcards or other shell meta-characters system()
does not use the shell and is not subject to your shell's word-splitting or expansion. And it's easy enough to avoid passing wildcards, as perl has its own built-in methods for expanding a glob or constructing an array full of filenames.
The only time you might need to quote variables is if you're combining them into one string. For example, you can add the first name and surname to the GECOS field with:
$ perl -lne '($surname,$first,$username,$group,$secgroup,$comments) = split /\s*,\s*/;
system("echo","useradd","-g",$group,"-G",$secgroup,"-c","$first $surname - $comments",$username)' Newbies.csv
and even then, there are many other ways of doing that - e.g. with the .
concatenation operator:
$first . " " . $surname . " - " . $comments
or using join()
:
join(" ",$first,$surname,"-",$comments)
Example output:
useradd -g Accounting -G Emp -c Adlai Esche - 5326 aesche
useradd -g Accounting -G Emp -c Kaila West - 0699 kwest
useradd -g MArketing -G Emp -c Markos July - 7208 mjuly
It's not obvious from the output shown here, but the comments are actually passed as a single argument to useradd
's -c
option. e.g. Adlai Esche - 5326
is a single argument to the -c
option, not four separate arguments, a single string that happens to contain space characters.
You can see this if you replace the "echo",
with "printf","%s\n",
so that it prints each arg on a separate line - e.g.
$ perl -lne '($surname,$first,$username,$group,$secgroup,$comments) = split /\s*,\s*/;
system("printf","%s\n","useradd","-g",$group,"-G",$secgroup,"-c","$first $surname - $comments",$username)' Newbies.csv
useradd
-g
Accounting
-G
Emp
-c
Adlai Esche 5326
aesche
...
An awk
version would be very similar. The main differences being that awk
automatically splits its input into columns ($1, $2, $3....) based on the Field Separator (FS
variable, defaults to whitespace but you can set to a comma with -F,
) and awk's system function only takes a single string argument so you'd have to do the quoting yourself, similar to what's required for sh or bash.
perl has a similar -F
option and can also auto-split its input into columns (into an array called @F
), but it's easier to read code with meaningful variable names like $group
rather than $F[2]
.
bash version:
#!/bin/bash
MY_INPUT='Newbies.csv'
while IFS=, read -r surname first username group secgroup comments; do
echo useradd -g "$group" -G "$secgroup" -c "$comments" "$username"
#echo useradd -g "$group" -G "$secgroup" -c "$first $surname - $comments" "$username"
#printf "%s\n" useradd -g "$group" -G "$secgroup" -c "$first $surname - $comments" "$username"
done < "$MY_INPUT"
Again, this is written as a dry-run. Remove the echo
to allow it to actually create the user accounts. It also has a comment showing how to add the first name and surname to the GECOS field.
Output is the same as in the perl version:
useradd -g Accounting -G Emp -c Adlai Esche - 5326 aesche
useradd -g Accounting -G Emp -c Kaila West - 0699 kwest
useradd -g MArketing -G Emp -c Markos July - 7208 mjuly
Or:
useradd -g Accounting -G Emp -c Adlai Esche - 5326 aesche
useradd -g Accounting -G Emp -c Kaila West - 0699 kwest
useradd -g MArketing -G Emp -c Markos July - 7208 mjuly
Combining bash and perl:
#!/bin/bash
MY_INPUT='Newbies.csv'
perl -lne '($surname,$first,$username,$group,$secgroup,$comments) = split /\s,\s/;
system("echo","useradd","-g",$group,"-G",$secgroup,"-c",$comments,$username)' "$MY_INPUT"
This is such a simple task that it doesn't really provide a good example of why it's better to use awk or perl or almost-anything-except-bash for text processing. See Why is using a shell loop to process text considered bad practice?
However, even here the perl version is faster (not really important unless you have thousands of accounts to create) and the split /\s*,\s*/
strips any leading and trailing whitespace from the comma-delimited fields - trivial in perl, significantly more work to do in bash. i.e. it "cleans up" the input data so that it is correct for the purpose we want to use it for - user and group names in unix may not start with a space.
useradd
command's own usage message, because the command you gave lacks a mandatory argument. In particular, you have specified no username for the new user (the"${A_USERNAME[$index]}"
argument in the answer you linked). – steeldriver Feb 20 '22 at 00:31useradd
command has no visibility of the variables declared in the script - it only sees the arguments that you pass to it. – steeldriver Feb 20 '22 at 01:37"${A_USERNAME[$index]}"
to the end of the adduser line. – cas Feb 20 '22 at 03:46