I'm attempting to write a KornShell (ksh) function that uses printf to pad a string to a certain width.
Examples:
Call
padSpaces Hello 10
Output
'Hello '
I currently have:
padSpaces(){
WIDTH=$2
FORMAT="%-${WIDTH}.${WIDTH}s"
printf $FORMAT $1
}
Edit: This seems to be working, in and of itself, but when I assign this in the script it seems to lose all but the first space.
TEXT=`padSpaces "TEST" 10`
TEXT="${TEXT}A"
echo ${TEXT}
Output:
TEST A
I'm also open to suggestions that don't use printf. What I'm really trying to get at is a way to make a fixed width file from ksh.
Your function works fine for me. Your assignment won't work with spaces around the equal sign. It should be:
SOME_STRING=$(padSpaces TEST 10)
I took the liberty of replacing the backticks, too.
You don't show how you are using the variable or how you obtain the output you showed. However, your problem may be that you need to quote your variables. Here's a demonstration:
$ SOME_STRING=$(padSpaces TEST 10)
$ sq=\'
$ echo $sq$SOME_STRING$sq
'TEST '
$ echo "$sq$SOME_STRING$sq"
'TEST '
Are you aware that you define a function called padSpaces, yet call one named padString? Anyway, try this:
padString() {
WIDTH=$2
FORMAT="%-${WIDTH}s"
printf $FORMAT $1
}
Or, the more compact:
padString() {
printf "%-${2}s" $1
}
The minus sign tells printf to left align (instead of the default right alignment). As the manpage states about the command printf format [ arg ... ],
The arguments arg are printed on standard output in accordance with the
ANSI-C formatting rules associated with the format string format.
(I just installed ksh to test this code; it works on my machineTM.)
Related
For personal development and projects I work on, we use four spaces instead of tabs.
However, I need to use a heredoc, and I can't do so without breaking the indention flow.
The only working way to do this I can think of would be this:
usage() {
cat << ' EOF' | sed -e 's/^ //';
Hello, this is a cool program.
This should get unindented.
This code should stay indented:
something() {
echo It works, yo!;
}
That's all.
EOF
}
Is there a better way to do this?
Let me know if this belongs on the Unix/Linux Stack Exchange instead.
(If you are using bash 4, scroll to the end for what I think is the best combination of pure shell and readability.)
For heredocs, using tabs is not a matter of preference or style; it's how the language is defined.
usage () {
⟶# Lines between EOF are each indented with the same number of tabs
⟶# Spaces can follow the tabs for in-document indentation
⟶cat <<-EOF
⟶⟶Hello, this is a cool program.
⟶⟶This should get unindented.
⟶⟶This code should stay indented:
⟶⟶ something() {
⟶⟶ echo It works, yo!;
⟶⟶ }
⟶⟶That's all.
⟶EOF
}
Another option is to avoid a here document altogether, at the cost of having to use more quotes and line continuations:
usage () {
printf '%s\n' \
"Hello, this is a cool program." \
"This should get unindented." \
"This code should stay indented:" \
" something() {" \
" echo It works, yo!" \
" }" \
"That's all."
}
If you are willing to forego POSIX compatibility, you can use an array to avoid the explicit line continuations:
usage () {
message=(
"Hello, this is a cool program."
"This should get unindented."
"This code should stay indented:"
" something() {"
" echo It works, yo!"
" }"
"That's all."
)
printf '%s\n' "${message[#]}"
}
The following uses a here document again, but this time with bash 4's readarray command to populate an array. Parameter expansion takes care of removing a fixed number of spaces from the beginning of each lie.
usage () {
# No tabs necessary!
readarray message <<' EOF'
Hello, this is a cool program.
This should get unindented.
This code should stay indented:
something() {
echo It works, yo!;
}
That's all.
EOF
# Each line is indented an extra 8 spaces, so strip them
printf '%s' "${message[#]# }"
}
One last variation: you can use an extended pattern to simplify the parameter expansion. Instead of having to count how many spaces are used for indentation, simply end the indentation with a chosen non-space character, then match the fixed prefix. I use : . (The space following
the colon is for readability; it can be dropped with a minor change to the prefix pattern.)
(Also, as an aside, one drawback to your very nice trick of using a here-doc delimiter that starts with whitespace is that it prevents you from performing expansions inside the here-doc. If you wanted to do so, you'd have to either leave the delimiter unindented, or make one minor exception to your no-tab rule and use <<-EOF and a tab-indented closing delimiter.)
usage () {
# No tabs necessary!
closing="That's all"
readarray message <<EOF
: Hello, this is a cool program.
: This should get unindented.
: This code should stay indented:
: something() {
: echo It works, yo!;
: }
: $closing
EOF
shopt -s extglob
printf '%s' "${message[#]#+( ): }"
shopt -u extglob
}
geta() {
local _ref=$1
local -a _lines
local _i
local _leading_whitespace
local _len
IFS=$'\n' read -rd '' -a _lines ||:
_leading_whitespace=${_lines[0]%%[^[:space:]]*}
_len=${#_leading_whitespace}
for _i in "${!_lines[#]}"; do
printf -v "$_ref"[$_i] '%s' "${_lines[$_i]:$_len}"
done
}
gets() {
local _ref=$1
local -a _result
local IFS
geta _result
IFS=$'\n'
printf -v "$_ref" '%s' "${_result[*]}"
}
This is a slightly different approach which requires Bash 4.1 due to printf's assigning to array elements. (for prior versions, substitute the geta function below). It deals with arbitrary leading whitespace, not just a predetermined amount.
The first function, geta, reads from stdin, strips leading whitespace and returns the result in the array whose name was passed in.
The second, gets, does the same thing as geta but returns a single string with newlines intact (except the last).
If you pass in the name of an existing variable to geta, make sure it is already empty.
Invoke geta like so:
$ geta hello <<'EOS'
> hello
> there
>EOS
$ declare -p hello
declare -a hello='([0]="hello" [1]="there")'
gets:
$ unset -v hello
$ gets hello <<'EOS'
> hello
> there
> EOS
$ declare -p hello
declare -- hello="hello
there"
This approach should work for any combination of leading whitespace characters, so long as they are the same characters for all subsequent lines. The function strips the same number of characters from the front of each line, based on the number of leading whitespace characters in the first line.
The reason all the variables start with underscore is to minimize the chance of a name collision with the passed array name. You might want to rewrite this to prefix them with something even less likely to collide.
To use in OP's function:
gets usage_message <<'EOS'
Hello, this is a cool program.
This should get unindented.
This code should stay indented:
something() {
echo It works, yo!;
}
That's all.
EOS
usage() {
printf '%s\n' "$usage_message"
}
As mentioned, for Bash older than 4.1:
geta() {
local _ref=$1
local -a _lines
local _i
local _leading_whitespace
local _len
IFS=$'\n' read -rd '' -a _lines ||:
_leading_whitespace=${_lines[0]%%[^[:space:]]*}
_len=${#_leading_whitespace}
for _i in "${!_lines[#]}"; do
eval "$(printf '%s+=( "%s" )' "$_ref" "${_lines[$_i]:$_len}")"
done
}
I'm practicing bash and honestly, it is pretty fun. However, I'm trying to write a program that compares an array's value to a variable and if they are the same then it should print the array's value with an asterisk to the left of it.
#!/bin/bash
color[0]=red
color[1]=blue
color[2]=black
color[3]=brown
color[4]=yellow
favorite="black"
for i in {0..4};do echo ${color[$i]};
if {"$favorite"=$color[i]}; then
echo"* $color[i]"
done
output should be *black
There's few incorrect statements in your code that prevent it from doing what you ask it to. The comparison in bash is done withing square brackets, leaving space around them. You correctly use the = for string comparison, but should enclose in " the string variable. Also, while you correctly address the element array in the echo statement, you don't do so inside the comparison, where it should read ${color[$i]} as well. Same error in the asterisk print. So, here a reworked code with the fixes, but read more below.
#!/bin/bash
color[0]=red
color[1]=blue
color[2]=black
color[3]=brown
color[4]=yellow
favorite=black
for i in {0..4};do
echo ${color[$i]};
if [ "$favorite" = "${color[$i]}" ]; then
echo "* ${color[$i]}"
fi
done
While that code works now, few things that probably I like and would suggest (open to more expert input of course by the SO community): always enclose strings in ", as it makes evident it is a string variable; when looping an array, no need to use index variables; enclose variables always within ${}.
So my version of the same code would be:
#!/bin/bash
color=("red" "blue" "black" "brown" "yellow")
favorite="black"
for item in ${color[#]}; do
echo ${item}
if [ "${item}" = "${favorite}" ]; then
echo "* $item"
fi
done
And a pointer to the great Advanced Bash-Scripting Guide here: http://tldp.org/LDP/abs/html/
Is there some way to access a variable by referring to it by a value?
BAR=("hello", "world")
function foo() {
DO SOME MAGIC WITH $1
// Output the value of the array $BAR
}
foo "BAR"
Perhaps what you're looking for is indirect expansion. From man bash:
If the first character of parameter is an exclamation point (!), a level of variable indirection is introduced. Bash uses the
value of the variable formed from the rest of parameter as the name of the variable; this variable is then expanded and that value
is used in the rest of the substitution, rather than the value of parameter itself. This is known as indirect expansion. The
exceptions to this are the expansions of ${!prefix*} and ${!name[#]} described below. The exclamation point must immediately fol‐
low the left brace in order to introduce indirection.
Related docs: Shell parameter expansion (Bash Manual) and Evaluating indirect/reference variables (BashFAQ).
Here's an example.
$ MYVAR="hello world"
$ VARNAME="MYVAR"
$ echo ${!VARNAME}
hello world
Note that indirect expansion for arrays is slightly cumbersome (because ${!name[#]} means something else. See linked docs above):
$ BAR=("hello" "world")
$ v="BAR[#]"
$ echo ${!v}
hello world
$ v="BAR[0]"
$ echo ${!v}
hello
$ v="BAR[1]"
$ echo ${!v}
world
To put this in context of your question:
BAR=("hello" "world")
function foo() {
ARR="${1}[#]"
echo ${!ARR}
}
foo "BAR" # prints out "hello world"
Caveats:
Indirect expansion of the array syntax will not work in older versions of bash (pre v3). See BashFAQ article.
It appears you cannot use it to retrieve the array size. ARR="#${1}[#]" will not work. You can however work around this issue by making a copy of the array if it is not prohibitively large. For example:
function foo() {
ORI_ARRNAME="${1}[#]"
local -a ARR=(${!ORI_ARRNAME}) # make a local copy of the array
# you can now use $ARR as the array
echo ${#ARR[#]} # get size
echo ${ARR[1]} # print 2nd element
}
BAR=("hello", "world")
function foo() {
eval echo "\${$1[#]}"
}
foo "BAR"
You can put your arrays into a dictionary matched with their names. Then you can look up this dictionary to find your array and display its contents.
I am new to shell scripts, I am trying to create a simple function which will return the concatenated two strings that are passed as parameters. I tried with below code
function getConcatenatedString() {
echo "String1 $1"
echo "String2 $2"
str=$1/$2
echo "Concatenated String ${str}"
echo "${str}"
}
//I am calling the above function
constr=$(getConcatenatedString "hello" "world")
echo "printing result"
echo "${constr}"
echo "exit"
I see the below output when running the script with above code,
printing result
String1 hello
String2 world
Concatenated String hello/world
hello/world
exit
If you look at the code I am first calling the function and then I am echoing "printing result" statement, but the result is first comes the "printing result" and echos the statement inside the function. Is the below statement calling the function
constr=$(getConcatenatedString "hello" "world")
or
echo ${constr}
is calling the function ?
Because if I comment out #echo ${constr} then nothing is getting echoed !!! Please clarify me.
The first is calling the function and storing all of the output (four echo statements) into $constr.
Then, after return, you echo the preamble printing result, $constr (consisting of four lines) and the exit message.
That's how $() works, it captures the entire standard output from the enclosed command.
It sounds like you want to see some of the echo statements on the console rather than capturing them with the $(). I think you should just be able to send them to standard error for that:
echo "String1 $1" >&2
paxdiablo's solution is correct. You cannot return a string from a function, but you can capture the output of the function or return an integer value that can be retrieved by the caller from $?. However, since all shell variables are global, you can simply do:
getConcatenatedString() { str="$1/$2"; }
getConcatenatedString hello world
echo "Concatenated String ${str}"
Note that the function keyword is redundant with (), but function is less portable.
A more flexible, but slightly harder to understand approach is to pass a variable name, and use eval so that the variable becomes set in the caller's context (either a global or a function local). In bash:
function mylist()
{
local _varname=$1 _p _t
shift
for _p in "$#"; do
_t=$_t[$_p]
done
eval "$_varname=\$_t"
}
mylist tmpvar a b c
echo "result: $tmpvar"
On my Linux desktop (bash-3.2) it's approx 3-5x faster (10,000 iterations) than using ``, since the latter has process creation overheads.
If you have bash-4.2, its declare -g allows a function to set a global variable, so you can replace the unpretty eval with:
declare -g $_varname="$_t"
The eval method is similar to TCL's upvar 1, and declare -g is similar to upvar #0.
Some shell builtins support something similar, like bash's printf with "-v", again saving process creation by assigning directly to a variable instead of capturing output (~20-25x faster for me).
I am trying to verify that the format of a variable is a number and is at least 10 digits long with leading zeros, inside of an expect script.
In a bash script it would look something like this:
[[ "$var" != +([0-9]) ]] && echo "bad input" && exit
while [[ $(echo -n ${var} | wc -c) -lt 10 ]] ; do var="0${var}" ; done
For the following input:
16
I am trying to achieve the following output:
0000000016
The simplest way to check whether a variable has just digits is to use a regular expression. Expect's regular expressions are entirely up to the task:
if {![regexp {^\d+$} $var]} {
puts "bad input"
exit
}
Padding with zeroes is best done by formatting the value; if you know C's printf(), you'll recognize the format:
set var [format "%010d" $var]
Expect is actually just an extension of TCL, so you can use any facility that TCL provides. TCL is an unusual language, but it's not hard to do what you want.
# Set a test string.
set testvar 1234567890
# Store the match (if any) in matchvar.
regexp {\d{10,}} $testvar matchvar
puts $matchvar
# Test that matchvar holds an integer.
string is integer $matchvar
The string is command is relatively new, so you might have to rely on the return value of regexp if your TCL interpreter doesn't support it.