4

If I understand correctly, the default separator for the output of awk is space.

However, the following script does not behave as I expect. I do not manage to parse the output of awk into an array:

#!/bin/bash
echo "------ with input string from awk ------"
ALL_TTY_OWNERS_STR=$(ls -l /dev | grep tty | awk '{print $3}')

read -r -a ALL_TTY_OWNERS_ARRAY <<< "$ALL_TTY_OWNERS_STR"

echo "${#ALL_TTY_OWNERS_ARRAY[@]}" # This says 1
echo "${ALL_TTY_OWNERS_ARRAY[0]}" # "root", as expected
echo "${ALL_TTY_OWNERS_ARRAY[1]}" # empty string, expected "root"
echo "${ALL_TTY_OWNERS_ARRAY[2]}" # empty string, expected "root"


echo "------ with my manually created input string ------"
ALL_TTY_OWNERS_STR="root root root" # only for testing

read -r -a ALL_TTY_OWNERS_ARRAY <<< "$ALL_TTY_OWNERS_STR"

echo "${#ALL_TTY_OWNERS_ARRAY[@]}" # 3, as expected
echo "${ALL_TTY_OWNERS_ARRAY[0]}" # "root", as expected
echo "${ALL_TTY_OWNERS_ARRAY[1]}" # "root", as expected
echo "${ALL_TTY_OWNERS_ARRAY[2]}" # "root", as expected

Why can't I parse the output of awk with read as I expected I could?

  • I don't know what your larger goal is, but I advise not writing a shell script. You're having problems with whitespace and you're attempting to use Bash arrays -- both of those are red flags that the problem you are trying to solve is too complicated for a shell script. If you have Bash, you have the option of using a better scripting language like Perl or Python. (And conversely, if you don't have access to anything but shell, you probably can't use Bash extensions.) – zwol Sep 7 '17 at 14:12
  • @zwol I am parsing the output of xrandr to get the names of connected output. Until recently I placed what I have directly in my .bashrc, now in a separate sh file. Why is it too complicated for bash? – Gauthier Sep 7 '17 at 15:28
5

It's about field separator.

You need to define record separator to put each string in single one. Use ORS param:

ls -l /dev | grep tty | awk 'BEGIN { ORS=" " }; {print $3}'

Without it you output will:

root
root
root
etc...

And when you define ALL_TTY_OWNERS_STR variable you put only first string of output in first element of array. Because of this you array will contain only one element and this is exactly what you are gets

  • I see. I got confused because echo ALL_TTY_OWNERS_STR after the assignment does output all on a single line. This makes it look like it's a space-separated string, but it isn't? – Gauthier Sep 7 '17 at 9:35
4

The default field separator for output is space. But the record separator is a new line. ALL_TTY_OWNERS_STR contains a bunch of roots separated by newlines:

$ printf "%q\n" "$ALL_TTY_OWNERS_STR"
$'root\nroot\nroot\n....

And read by default reads upto the first newline.

If you just want all the users in ALL_TTY_OWNERS_ARRAY, it might be simpler to do something like:

ALL_TTY_OWNERS_ARRAY=( $(stat -c '%U' /dev/*tty*) )
  • oh, I used echo instead of printf, and the \n seem to be replaced by a space in the output of my term! – Gauthier Sep 7 '17 at 9:37
3

read -a array reads all the words from one record into array.

If you want to read all the words from the whole input, use a record separator that doesn't occur in the input, like with -d '' that uses the NUL character as the record separator, or -d : (as : cannot occur in a username).

IFS=$'\n' read -rd '' -a array < <(ls -Ll /dev | awk '/tty/{print $3}')

(here using -L so that for symlinks, the ownership of the devices as opposed to the symlink is returned).

Or use readarray for each line of the input to be stored in the array.

readarray -t array < <(ls -Ll /dev | awk '/tty/{print $3}')

Or you could use the split+glob operator:

set -o noglob # disable glob part
IFS=$'\n'     # split on newline
array=($(ls -Ll /dev | awk '/tty/{print $3}')) # invoke split+glob by leaving
                                               # the $(...) unquoted.

Note that the tty is looked for in the whole output of ls -Ll, so including user and group name, file name (and target of symlink if you omit the -L). If you want to consider only the filename, you could do

ls -Lld /dev/*tty* | awk '{print $3}'

instead.

With zsh, you could do:

zmodload zsh/stat
stat -s -A array +uid /dev/*tty*

(which would have the benefit of also working for user names containing blanks).

0

If you don't quote the variable after <<<, the newlines will become the whitespace you expect and the result will be all on one line:

read -r -a ALL_TTY_OWNERS_ARRAY <<< $ALL_TTY_OWNERS_STR
  • 1
    That was a bug, fixed in bash4.4. bash4.4 now preserves the newlines like in other shells. BASH_COMPAT=4.3 won't restore the old behaviour. – Stéphane Chazelas Sep 7 '17 at 8:33
  • Good to know, thanks. I was using 4.3.42. – meuh Sep 7 '17 at 8:43

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.