2

I trying to create a list of strings with spaces in, that I want to choose between in a select - something like this:

sel=""
while read l   
do
  sel=$(printf "%s '%s'" "$sel" "$l")
done< <(cd /some/data/directory;du -sh *) 

select x in $sel
do
  break
done

The string sel looks like expected: "597G 2022" "49G analysis" "25K @Recycle", but the select looks like:

1) "597G      3) "49G       5) "25K
2) 2022"      4) analysis"  6) @Recycle"
#?

What I want to achieve is of course something like:

1) 597G 2022
2) 49G  analysis
3) 25K  @Recycle
#?

And more generally, something where you can select between strings built from several data sources in some way. I have looked for inspiration in several places, like here, but it doesn't quite work for my case.

Edit

I forgot to mention, this bash is rather old (and I can't update it, sadly):

[admin@CoMind-UniCron ~]# bash --version
GNU bash, version 3.2.57(1)-release (x86_64-QNAP-linux-gnu)
Copyright (C) 2007 Free Software Foundation, Inc.
2

2 Answers 2

5

You want to use an array of multiple strings there, not a single string that must be split correctly by the shell. Something like this:

#!/bin/bash

while read l   
do
  sel+=( "$l" )
done< <(cd /some/data/directory;du -sh *) 

select x in "${sel[@]}"
do
  break
done

Which produces the expected output:

$ foo.sh
1) 597G 2022
2) 50G  analysis
3) 32K  @Recycle
#? 

A safer approach, that can handle arbitrary file/dir names except newlines and is only a little more complex but can be used without worried in all situations is:

#!/bin/bash

while IFS= read -r l   
do
  sel+=( "$l" )
done< <(shopt -s nullglob dotglob; cd /some/data/directory && du -sh -- *) 

select x in "${sel[@]}"
do
  break
done
8
  • Nice one! I'll give that a try. I've accepted your answer, because it looks like it does exactly what I want. Commented Jan 6, 2023 at 17:32
  • @j4nd3r53n thanks, but do come back and unaccept if it doesn't work for your version of bash for some reason. I doubt it, simple arrays have been in bash for ages, but I am not 100% sure. I know associative arrays were added in version 4 and I would be very surprised if version 3 didn't have indexed arrays, but since I'm not sure... Commented Jan 6, 2023 at 17:40
  • 2
    Arrays are supported in Bash 3.2, and so should the += assignment operator for adding to an array be. Commented Jan 6, 2023 at 18:02
  • Using read without -r doesn't make sense here. You're also missing a -- and not checking the exit status of cd, See also readarray -td '' sel < <(cd ... && du -sh0 -- *) Commented Jan 6, 2023 at 18:24
  • 2
    @terdon, arrays were added in bash in 2.0, years after zsh and decades after csh or ksh but still over 25 years ago. += (initially from zsh IIRC) was added in 3.1 Commented Jan 6, 2023 at 18:29
1

This is really over-thought.

#!/usr/bin/env bash

IFS='
'
select x in $(cd /some/data/directory && du -sh -- *); do
    break
done
unset IFS  # or save/restore it explicitly

As an added bonus, the only bashism here is the select.

Typescript:

$ ./cg
 1) 0   0
 2) 0   000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
 3) 0   000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
 4) 0   000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a
 5) 0   000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ą
 6) 60k 1028501896.pdf
 7) 0   a
 8) 16k a.bkp
 9) 36k a.cpio
10) 10k a.d
11) 36k a.patch
12) 60k a.pax
13) 84k a.png
14) 4k  b.cpio
15) 32k b.pax
16) 12k b.tar
17) 0   bugreport.cgi?bug=910770;mbox=yes;mboxmaint=yes
18) 74.2M       build-output
19) 796k        busybox
20) 428k        busybox_1%3a1.30.1-6+b3_amd64.deb
21) 0   CB_Unix
22) 4k  cg
#? 

The quoting like you're doing doesn't work because quote removal applies only to the original word; the result of parameter expansion is only field-split (and globbed). To do that, you need to tokenise the string as input:

eval "select x in $sel
do
  break
done"

and for obvious reasons you really shouldn't do this, since you haven't actually escaped the filenames:

$ ./cg
./cg: eval: line 11: unexpected EOF while looking for matching `''
./cg: eval: line 15: syntax error: unexpected end of file

if you really need to fork a process for each line and really need to store the du output in a big string then you should've done

#!/usr/bin/env bash

sel=
while read -r l; do
    sel="$(printf '%s %q' "$sel" "$l")"
done < <(cd /some/data/directory && du -sh -- *) 

eval "select x in $sel; do
  break
done"

(I've disabled escape-mangling the paths, which you had on for some reason.)

You must log in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.