2

I am trying to create a select in bash that lets you select items from a .txt file. Each item is a new line, I read the items and put them in an array using:

items=()
  while read -r line; do
  items+=("$line")
done <items.txt

this works fine.

After that I put them in a select using:

PS3="Choose Item > "
 select item in "${items[@]}" "Cancel"; do
  case ${item} in
   Cancel)
     echo "You chose to cancel"
     break
     ;;
   *)
     echo "You chose ${item}"
     break
     ;;

 esac
done

The select displays all items line by line. This all works fine until there are more than 9 items. When there are more than 9 items it displays them really weird. It also combines item names. See picture below.

IMG: It displays weird

IMG: Less than 9 items

I read online it maybe has to do something with how bash reads the numbered argument. But I couldn't find any sollutions. Why do bash command line arguments after 9 require curly brackets?

Does someone know how to fix the problem of only being able to have up to 9/10 options in the bash select?

Léa Gris
  • 17,497
  • 4
  • 32
  • 41

2 Answers2

6

Seems like your file items.txt has Windows line endings (\r\n) instead of Linux line endings (\n). Convert it to Linux line endings using dos2unix items.txt.

For your 10 items, bash displays the choices in multiple columns. The output should look like ...

1) item1     3) item3    5) item5    7) item7    9) item9
2) item2     4) item4    6) item6    8) item8   10) item10

... but due to the Windows line endings each item has a trailing \r which causes the terminal to reset the cursor to the start of the line and overwrite the already printed text. Therefore, only the last item in each line is visible.

By the way: To read a file into an array you don't need a loop. mapfile -t array < file does the same thing but faster and with less code.

Socowi
  • 25,550
  • 3
  • 32
  • 54
4

As Socowi said, or alternatively add both carriage-return \r and line-feed \n to the Internal Field Separator IFS environment variable.

items=()
  while IFS=$'\r\n' read -r line; do
  items+=("$line")
done <items.txt

Or to populates the items array all at once regardless of it using DOS or Unix line ending:

IFS=$'\r\n' read -r -d '' -a items <items.txt
Léa Gris
  • 17,497
  • 4
  • 32
  • 41