0

So, I want to be able to take inputs from users where users will input space separated strings or values and then go through those values one by one and convert them into a format required by one of the commands I have. I want something like this: For example

Users will be prompted with something like this:

Specify the package: perl runtime pool tools

I just input 4 strings. Now for my output I want something like this :

Pkg1=perl,Pkg2=runtime,Pkg3=pool,Pkg4=tools

The Pkg basically increments by 1 depending on the number of inputs users put.

Is this possible? How can I achieve this in bash?

Any help will be highly appreciated! Thank you for your time!

1
  • Try to implement as much as you can and replace the parts you don't know with comments explaining what you want. Then edit your question, copy&paste your incomplete or non-working code to the question and format it as a code block.
    – Bodo
    Jun 30, 2022 at 21:47

4 Answers 4

2

First of all don't prompt! Prompting for data is very rarely a good idea: it is very easy to make a mistake, a typo, the command cannot be repeated, you can't use tab completion for file names and so on. That just makes the life of your users, and your own, more difficult for no reason. Just have the users pass the package names as arguments instead:

#!/bin/bash

packageString="Pkg1=$1"
shift

for (( i=1; i<=$#;i++)); do
  packageString+=";Pkg"$((i+1))"=${!i}"
done
echo "$packageString";

Running the above script with the input in your question results in:

$ foo.sh perl runtime pool tools
Pkg1=perl;Pkg2=runtime;Pkg3=pool;Pkg4=tools
2

I'm echoing terdons plea to not prompt the user interactively without reason. In this case, it's easier for the user to give the strings as arguments to a script since it gives them the option of recalling the command from the shell's command-line history or using the shell's tab completion, general editing capabilities, etc.

The below script uses a single awk command.

The awk command takes the given strings as its command-line arguments, and formats each of them individually in a loop. The purpose of the loop is to build a record of fields, each field being some string PkgN=something where N is an integer counter and something is one of the given strings.

When the loop is finished, print outputs the completed record. The delimiter assigned to OFS is used between the fields.

#!/bin/sh

awk -v OFS=, '
BEGIN {
        for (i = 1; i < ARGC; ++i) $i = sprintf("Pkg%d=%s", i, ARGV[i])
        print
}' "$@"

The same idea, but using a shell loop:

#!/bin/sh

i=0
for pkg do
    i=$(( i + 1 ))
    set -- "$@" "Pkg$i=$pkg"
    shift
done

IFS=,
printf '%s\n' "$*"

This replaces each of the positional parameters (the arguments to the script) with the new string and ends with printing them all with commas as delimiters.

Both of these scripts would output an empty line if no arguments are given. You may avoid that by testing for arguments at the start of the script and exiting if there are none:

[ "$#" -ne 0 ] || exit

The way the test is done here would ensure that the script is terminated with a non-zero exit status if it's called with no arguments.

Testing:

$ ./script perl runtime pool tools
Pkg1=perl,Pkg2=runtime,Pkg3=pool,Pkg4=tools
1
  • Thank you for the help! This worked! Also, great explanation!
    – BishwashK
    Jul 1, 2022 at 14:18
1

A script - named script.sh in the example below - containing the following code should get you the output you described:

#!/bin/bash

#USER INPUT
read -p "Specify the package: " -a vals

#COUNTER
i=1

#ARRAY ELEMENT LOOP
for v in "${vals[@]}"; do

   #Printing output  
   echo -n "Pkg$i=$v"

   #Increment counter and add the comma, or break out of loop if $i==
   i=$((i+1))
   if [[ $i -le ${#vals[@]} ]]; then
        echo -n ","
   else
        break
   fi

done

To run the script from the shell, you need to first make it executable (chmod) and then execute it:

chmod +x script.sh
./script.sh

Its input/output will look like this:

Specify the package: perl runtime pool tools
Pkg1=perl,Pkg2=runtime,Pkg3=pool,Pkg4=tools
1
  • Perfect and straightforward! Great explanation and it worked! Thanks!
    – BishwashK
    Jul 1, 2022 at 14:18
0
$ perl -le 'print join ",", map { "Pkg" . ++$i . "=$_" } @ARGV' perl runtime pool tools
Pkg1=perl,Pkg2=runtime,Pkg3=pool,Pkg4=tools

map { "Pkg" . ++$i . "=$_" } @ARGV returns a list where each element is one of the command-line arguments prefixed by the literal string Pkg, a counter variable ($i), which is auto-incremented on each pass through the map loop, and a literal = symbol. See perldoc -f map for details on how it works.

The list is joined with commas as separators and then printed. See perldoc -f join.

The -l option enables automatic line-ending processing. For this script, since there is no stdin to process, all that means is that a newline is automatically output with each print statement (normally, perl's print does not output a newline unless you explicitly include it). See man perlrun and search for -l\[octnum\].


If you want to accept user input first (which, as others have pointed out, is not recommended because it's hugely annoying and inconvenient to users), then you could do it with something like:

#!/bin/bash

# read the user input into array variable "$input"
read -p "Specify the package: " -r -a input

# now pass the array to the perl one-liner
perl -le 'print join ",", map { "Pkg" . ++$i . "=$_" } @ARGV' -- "${input[@]}"

The -- prevents perl from interpreting any of of the user input as options, just in case the user accidentally (or maliciously) enters something like a perl command-line option - e.g. -e ';system("rm -rf /");'. Never trust user-supplied input.

and, as usual, if you need to capture the output of the perl script into another shell variable, use command substitution:

var=$(perl -le 'print join ",", map { "Pkg" . ++$i . "=$_" } @ARGV' -- "${input[@]}")
printf "%s\n" "$var"

You must log in to answer this question.

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