split-sequence- delimit sequence by double space - arrays

I know how to delimit a string sequence by a space:
(defun ff-cols (dir file)
(with-open-file (ff-cols-str pathname :direction :input)
(length (split-sequence #\Space (read-line ff-cols-str nil 'eof)))))
But how do you delimit a sequence by a double space? Often flat files have
columns separated by double spaces.
(split-sequence " " "1 2 3 4")
returns
("1 2 3 4") ;
10
Also,
(split-sequence #\Space "1 2 3 4")
returns
("1" "" "2" "" "3" "" "4") ;
10

Try this instead:
(split-sequence-if (lambda (s) (equal s " ")) "1 2 3 4")
Or this:
(split-sequence #\Space "1 2 3 4" :remove-empty-subseqs t)

(ql:quickload "cl-ppcre")
(cl-ppcre:split "\\s\\s" "One Two Three Four Five")
("One" "Two Three" "Four" " Five")
Obviously, whatever you could've learned from other languages which also make use of regular expressions applies.

This could be because your string is not seperated by double spaces
(split-sequence " " "1 2 3 4")
try
(split-sequence " " "1 2 3 4")

Related

Reading array in a loop and ignoring spaces in favor of a newline

I am trying to load a variable '$LIST' that contains an array using the 'for' loop. However, I don't want them to be separated at spaces, but at the point of a new line.
How can I get this effect?
LIST=( \
"1" "Some text" "ON" \
"2" "Some text" "OFF" \
"3" "Some text. Some text" "ON" \
"4" "Some text" "OFF" \
"5" "Some text. Some text" "OFF" \
)
for ENTRY in "${LIST[#]}"
do
echo "$ENTRY"
done
I currently gets the following result:
1
Some text
ON
2
Some text
OFF
3
Some text. Some text
ON
4
Some text
OFF
5
Some text. Some text
OFF
And I would like to get this:
1 Some text ON
2 Some text OFF
3 Some text. Some text ON
4 Some text OFF
5 Some text. Some text OFF
Simply use printf:
$ printf '%s %s %s\n' "${LIST[#]}"
1 Some text ON
2 Some text OFF
3 Some text. Some text ON
4 Some text OFF
5 Some text. Some text OFF
printf iterates as long as all arguments have not been consumed.
First of all you don't need to use line continuation back-slashes \ to arrange the declaration of your list array.
Next, non exported variables are best entirely lower-case.
To iterate your list/array by groups of 3 element; passing the array elements as arguments to a function allows to do what you want.
#!/bin/bash
list=(
"1" "Some text" "ON"
"2" "Some text" "OFF"
"3" "Some text. Some text" "ON"
"4" "Some text" "OFF"
"5" "Some text. Some text" "OFF"
)
do_output () {
# iterates while there are still arguments
while [ $# -gt 0 ]
do
# prints 3 arguments
printf '%s %s %s\n' "$1" "$2" "$3"
# shifts to the next 3 arguments
shift 3
done
}
do_output "${list[#]}"
Put each line in quotes, not each word. You can add double quotes around the string of multiple words that should be the description.
LIST=(
'1 "Some text" ON'
'2 "Some text" OFF'
'3 "Some text. Some text" ON'
'4 "Some text" OFF'
'5 "Some text. Some text" OFF'
)

How to pass multiple arguments including arrays to a function in Bash where order is not defined and array elements can have multiple words?

I'm for hours trying to figure this one out.
What I would like to do is have a function that can receive N parameters which can include arrays.
Arrays can be in any parameter position, not only the last!
Order of parameters can change, for example, I can pass the first parameter as an array one time and in another call pass as second parameter!
Arrays and normal parameters must be interchangeable!
Example code (Note in this example parameter order is fixed, see following example for problem):
# $1 Menu title
# $2 List of menu options
# $3 Menu text
menu() {
dialog --clear --stdout --title "$1" --menu "$3" 0 0 0 "$2"
}
options=(
1 "Option 1"
2 "Option 2"
3 "Option 3"
4 "Option 4")
menu "Title" ${options[#]} "Text"
The first example can be solved like this:
# $1 Menu title
# $2 List of menu options
# $3 Menu text
menu() {
local -n a=$2
dialog --clear --stdout --title "$1" --menu "$3" 0 0 0 "${a[#]}"
}
options=(
1 "Option 1"
2 "Option 2"
3 "Option 3"
4 "Option 4")
menu "Title" options "Text"
Problem example:
my_func() {
my_command "$1" --something "$2" "$3" --somethingelse "$4"
}
optionsa=(
1 "Option 1"
2 "Option 2"
3 "Option 3"
4 "Option 4")
optionsb=(
1 "Option 1"
2 "Option 2"
3 "Option 3"
4 "Option 4")
my_func "Title" ${optionsa[#]} "Text" ${optionsb[#]}
# Note that I could do this and it must work too:
my_func ${optionsa[#]} "Title" ${optionsb[#]} "Text"
# This too must work:
my_func ${optionsa[#]} ${optionsb[#]} "Title" "Text"
When the array is expanded it must be possible to use the values as parameters to commands, if the value in the array has multiple words quoted ("Option 1") it must be treated as a single parameter to avoid for example passing a path as "/my dir/" and having it separated as </my> <dir/>.
How can I solve the second example, where the order or parameter can vary?
How can I solve the second example, where the order or parameter can vary?
There are two universal independent of programming language solutions to join a variadic length of a list of items together that any programmers should know about:
Pass the count...
my_func() {
local tmp optionsa title text optionsb
tmp=$1
shift
while ((tmp--)); do
optionsa+=("$1")
shift
done
title="$1"
shift
tmp=$1
shift
while ((tmp--)); do
optionsb+=("$1")
shift
done
text=$1
my_command "${optionsa[#]}" "$title" "${optionsb[#]}" "$text"
}
my_func 0 "Title" 0 "Text"
my_func 0 "Title" "${#optionsb[#]}" "${optionsb[#]}" "Text"
my_func "${#optionsa[#]}" "${optionsa[#]}" "Title" "${#optionsb[#]}" "${optionsb[#]}" "Text"
Use a sentinel value.
my_func() {
local tmp optionsa title text optionsb
while [[ -n "$1" ]]; do
optionsa+=("$1")
shift
done
title=$1
shift
while [[ -n "$1" ]]; do
optionsb+=("$1")
shift
done
text=$1
my_command "${optionsa[#]}" "$title" "${optionsb[#]}" "$text"
}
my_func "" "Title" "" "Text"
my_func "" "Title" "${optionsb[#]}" "" "Text"
my_func "${optionsa[#]}" "" "Title" "${optionsb[#]}" "" "Text"
And I see two bash specific solutions:
Pass arrays as names and use namereferences.
my_func() {
# Use unique names to avoid nameclashes
declare -n _my_func_optionsa=$1
local title=$2
declare -n _my_func_optionsb=$3
local title=$4
my_command "${_my_func_optionsa[#]}" "$title" "${_my_func_optionsb[#]}" "$text"
}
# arrays have to exists
my_func optionsa "Title" optionsb "Text"
Parse the arguments like a real man. This is actually universal solution, as it generally performs data serialization when creating list of arguments and then data deserialization when reading the arguments - the format (arguments as options) is specific to shell.
my_func() {
local args
# I am used to linux getopt, getopts would work as well
if ! args=$(getopt -n "$my_func" -o "a:t:b:x:" -- "$#"); then
echo "my_func: Invalid arguments" >&2
return 1
fi
set -- "$args"
local optionsa title optionsb text
while (($#)); do
case "$1" in
-a) optionsa+=("$2"); shift; ;;
-t) title="$2"; shift; ;;
-b) optionsb+=("$2"); shift; ;;
-x) text="$2"; shift; ;;
*) echo "my_func: Error parsing argument: $1" >&2; return 1; ;;
esac
shift
done
my_command "${optionsa[#]}" "$title" "${optionsb[#]}" "$text"
}
my_func -a opta1 -a opta2 -t Title -b optb1 -b optb2 -x text
# or build the options list from arrays:
# ie. perform data serialization
args=()
for i in "${optionsa[#]}"; do
args+=(-a "$i")
done
args+=(-t "title")
for i in "${optionsb[#]}"; do args+=(-b "$i"); done
args+=(-x "text")
my_func "${args[#]}"
Generally if a function has constant and small count of arguments, just use the arguments. If functions get complicated with more edge cases, I recommend to parse the arguments like a man - makes the function versatile and abstract, easy to expand and implement edge cases and handle corner cases and errors, easy to understand by other programmers, easily readable and parsable by human eyes.
Because your example codes may some problems, I recommend to research how does quoting work in shelll, specifically how "${array[#]}" differ from ${array[#]}, research how [#] differ from [*], how does and when word splitting expansion is performed and how it affects the parameters. All the unquoted array expansions in your code suffer from word splitting - the spaces will not be preserved, also in the first example.

array manipulation using indirection [duplicate]

This question already has answers here:
How to iterate over an array using indirect reference?
(7 answers)
Closed 3 years ago.
I'd like to use arrays in BASH properly when using indirection ${!varname}.
Here's my sample script:
#!/bin/bash
i="1 2 3"
x=CONFIG
y1=( "A and B" "B and C" )
# y1=( "\"A and B\"" "\"B and C\"" )
y2=( "ABC and D" )
y3=
echo "y1=${y1[#]}"
echo "y2=${y2[#]}"
echo "y3=${y3[#]}"
echo "==="
for z in $i
do
t=y${z}
tval=( ${!t} )
r=0
echo "There are ${#tval[#]} elements in ${t}."
if [ ${#tval[#]} -gt 0 ]; then
r=1
echo "config_y${z}=\""
fi
for tv in "${tval[#]}"
do
[ -n "${tv}" ] && echo "${tv}"
done
if [ "x$r" == "x1" ]; then
echo "\""
fi
done
Here's the result:
y1=A and B B and C
y2=ABC and D
y3=
===
There are 3 elements in y1.
config_y1="
A
and
B
"
There are 3 elements in y2.
config_y2="
ABC
and
D
"
There are 0 elements in y3.
What I would like to get instead is:
y1=A and B B and C
y2=ABC and D
y3=
===
There are 2 elements in y1.
config_y1="
A and B
B and C
"
There are 1 elements in y2.
config_y2="
ABC and D
"
There are 0 elements in y3.
I also tried to run something like this:
#!/bin/bash
i="1 2 3"
x=CONFIG
y1=( "A and B" "B and C" )
# y1=( "\"A and B\"" "\"B and C\"" )
y2=( "ABC and D" )
y3=
for variable in ${!y#}
do
echo "$variable" # This is the content of $variable
echo "${variable[#]}" # So is this
echo "${!variable}" # This shows first element of the indexed array
echo "${!variable[#]}" # Not what I wanted
echo "${!variable[0]} ${!variable[1]}" # Not what I wanted
echo "---"
done
Ideally, ${!Variable[#]} should do what I want, but it doesn't.
Also, ${!Variable} only shows the first element of the array,
What can I try?
Your syntax for accessing arrays is wrong here:
tval=( ${!t} )
This will evaluate to e.g. $y1 but you want "${y1[#]}" which is how you address an array with that name with proper quoting.
Unfortunately, there is no straightforward way to refer to an array through indirection, but see Indirect reference to array values in bash for a couple of workarounds.
Notice also how
y3=()
is different from
y3=
Using variables for stuff which isn't actually variable is a bit of a code smell, too.
#!/bin/bash
i="1 2 3"
x=CONFIG
y1=( "A and B" "B and C" )
y2=( "ABC and D" )
# Fix y3 assignment
y3=()
echo "y1=${y1[#]}"
echo "y2=${y2[#]}"
echo "y3=${y3[#]}"
echo "==="
for z in $i
do
# Add [#] as in Aaron's answer to the linked question
t=y${z}[#]
# And (always!) quote the variable interpolation
tval=( "${!t}" )
r=0
echo "There are ${#tval[#]} elements in ${t}."
if [ ${#tval[#]} -gt 0 ]; then
r=1
echo "config_y${z}=\""
fi
for tv in "${tval[#]}"
do
[ -n "${tv}" ] && echo "${tv}"
done
if [ "x$r" = "x1" ]; then
echo "\""
fi
done
Probably also investigate printf for unambiguously printing values.

bash string, separated by spaces, to array

I want to take a value read in and separate it from its spaces, then do stuff depending on the parts of the array. More exactly, if statement on the first (if false skip rest) 3 element will be changed from a word to a number using another program (a look up database) the 4th and last will check if there is a non-number or number above 64, if there isn't then it will combine it all back together (which I know how to do) then continue on. I have been working on this for over 3 hours now across multiple websites from Google.
vzybilly#vzybilly-laptop:~/Desktop$ cat ./test.sh
#!/bin/bash
read -p "cmd: " IN
#OIFS=$IFS
#IFS=';'
#arr2=$IN
#a=$(echo $IN | tr " " "\n")
a=$(echo "$IN")
for i in $(seq 0 $((4 - 1))); do
echo "a[$i] = \"${a[$i]}\""
done
#IFS=$OIFS
exit 0
vzybilly#vzybilly-laptop:~/Desktop$ ./test.sh
cmd: cmd pers item num
a[0] = "cmd pers item num"
a[1] = ""
a[2] = ""
a[3] = ""
What I want:
vzybilly#vzybilly-laptop:~/Desktop$ ./test.sh
cmd: cmd pers item num
a[0] = "cmd"
a[1] = "pers"
a[2] = "item"
a[3] = "num"
Instead of
a=$(echo "$IN")
use
a=($IN)

batch: multiple FOR parameter taken from command arguments

this is what I'm trying to do
find.bat:
#echo off
SET for_argument=%1
SET other_argument2=%2
SET other_argument3=%3
FOR %%A IN (%for_argument%) DO (
echo %%A
rem do other stuff
)
What I want to do is call
find.bat "1 2 3 4" arg2 arg3
and I want that FOR to be executed with 1 2 3 4 as separated arguments, so that the output is
1
2
3
4
But unfortunately with this code the output is
"1 2 3 4"
Can you help me?
Thanks!
SET "for_argument=%~1"
So you get in the for argument a b c d, but without the quotes, this is important for the FOR loop.
A quoted string like "a b c d" is handled as one token, but a b c d is split into four tokens, allowed delims are space "," ";" or "=".

Resources