Trouble converting string to int and using it on a for loop - sh

I'm a shell script beginner here, and I'm having some trouble with something - oddly specific - that I haven't found help for.
For sheer fun, I tried to code something that you feed a string into and that runs that through yes and lolcat, for cool rainbow text.
Something like this.
The code is pretty straightforward, and uses $((...)) to convert a string (the number of times the user wants its string to be repeated) to an integer.
Anyways, here's the code. I've tried moving the point in the script where the string is converted, but nothing.
echo Lolcat_repeater: A wonderful tool that does absolutely nothing.
echo [I] Input a string to lol:
echo [P] :: && read input
echo [I] Times to repeat [ENTER for endless]:
echo [P] :: && read repeats
$repeats=$(($repeats))
if [ $repeats == "" ]
then
yes $input | lolcat
else
for ((i=1; i<=$repeats; i++))
do
echo $input | lolcat
done
fi
This error always pops up:
./lolcat_repeater.sh: 11: Syntax error: Bad for loop variable
And, judging by how the code doesn't execute yes $input | lolcat when I just hit ENTER, I clearly messed something up in the conditional.
I know I probably sound stupid right now, but any help will be appreciated.
Thanks!

Related

How to echo bare hypen (`-`) in zsh?

I'm looking for a way to work around zsh echo's apparently treating a string that is just a hyphen as if it were an empty string
echo -
# no output
echo "-"
# no output
echo '-'
# no output
Specifically, I'm splitting a string at a known character and then working with the two pieces, and either of the two pieces could be -. Like
% my_f() {
my_arr=(${(s.b.)1})
echo $my_arr[1]
echo $my_arr[2]
}
% my_f "abc"
a
b
% my_f "-bc"
# I need to know -
b
% my_f "ab-"
a
# I need to know -
%
In the particular thing I'm working on, I can rework things so that the potential - isn't echo'd on its own
my_arr=(${(qqqs.b.)1})
echo " ${(Q)my_arr[1]} "
echo " ${(Q)my_arr[2]} "
But that feels like luck and will take sprinkling a lot of qqq and Q around this script. Is there a better way?
Try echo - "-". The first dash terminates option processing, so following text is printed.
See this excellent answer for more context: https://stackoverflow.com/a/57656708/11776945
Use printf instead. (Which is generally good advice regarding any use of echo.)
my_f () {
printf '%s\n' "${(s.b.)1}"
}

Variables may not be used as commands

Using fish shell, I'm writing very simple script that checks the command execution
#!/usr/bin/fish
command
if $status
echo "Oops error"
else
echo "Worked OK"
#...
end
And get the error message:
fish: Variables may not be used as commands. Instead, define a function like “function status; 0 $argv; end”. See the help section for the function command by typing “help function”.
The message looks pretty straight forward but no "defining function like..." nor "help function" helps solving the problem.
There is also a 'test' command, that sounds promising. But docs say it is to be used to check files...
How this simple thing should be done with fish shell?
Heh... And why all documentation is SO misleading?..
P.S. Please, don't write about 'and' command.
Fish's test command currently works exactly like POSIX test (i.e. the one you'll find in bash or similar shells). It has a couple of operations, including "-gt", "-eq", "-lt" to check if a number is bigger, equal or less than another number, respectively.
So if you want to use test, you'll do if test $status -eq 0 (a 0 traditionally denotes success). Otherwise, you can check the return value of a command by putting it in the if clause directly like if command (which will be true if the command returns 0) - that's what fish is trying to do here, which is why it complains about a variable being used in place of a command.

String inclusion in Fish Shell

I've looked at all the similar questions on Stack Overflow, and tried a bunch of stuff in their documentation. My present attempt is below.
What I'm trying to accomplish is differential behavior based on the computer user. I have a work computer and a home computer, and I want the work computer to go to a different directory when I enter "code" into the command line than the one I want when I enter it at home. I tried this (my work username is afk, home is sfk):
function code
set result (dirh)
if [contains "sfk" $result]
cd ~/rails
else if [contains "afk" $result]
cd ~/code
else
echo $result
end
end
Currently, this is griping that "Unknown command contains Users/sfk/.config/fish/" (My PWD is Users/sfk/.config/fish/ when I'm entering this). I think maybe contains is just for lists, but the docs aren't particularly clear about this? Any idea how I might do this?
EDIT
Just tried this, and while it no longer errors, it ends up in the else case, which it shouldn't, cause my user IS sfk, which IS included in the dirh string:
function code
set result (dirh)
if test (contains "sfk" $result)
cd ~/rails
else if test (contains "afk" $result)
cd ~/code
else
echo $result
end
end
As said above in the comments, that is the best solution for this specific use case. But to answer the question in case someone wants to do something else.
You are right about contains, that it does an exact match on list items. You could use the switch function instead. Which supports wild-card matching.
function code
set -l result (dirh)
switch $result
case '*sfk*'
cd ~/rails
case '*afk*'
cd ~/code
case '*'
echo $result
end
end

how do I get rid of single quotes around a doubly quoted string in perl? e.g ' "json_text" '

So I am having a ruby app write json response to the console which is read by another perl program that tries to convert the json response back to a perl hash. Here's my problem:
ruby app outputs the correct json output but the console adds a single quote to it like so:
my $ruby_json_out = '"{\"return\":{\"sync_enabled\":false,\"local\":true,\"name\":{\"name\":\"Sam\"}}}"'
my $ret = JSON->new->allow_nonref->decode($ruby_json_out);
Now I expect to get hash_ref in $ret but I get a string: '{"return":{"sync_enabled":false,"local":true,"name":{"name\":"Sam"}}}'.
I have searched all over the net and can't find a solution to this. When I manually stripout the single quote:
"{\"return\":{\"sync_enabled\":false,\"local\":true,\"name\":{\"name\":\"Sam\"}}}",
and run it works.
I am stuck on this for more than a day now and it's driving me crazy. I am new to perl and ruby too so I might be missing something. Any help will be appreciated greatly.
Why do you try to solve the problem on the Perl side? Woudln't it be easier to solve it on the Ruby side?
At any rate, you can use regexps to remove those doble quotes in the same way you do it manually:
my ($good_json) = ($ruby_json_out =~ /^"(.+?)"$/ ;
And then
$good_json=~ s/\\"/"/g;
Which results in
x JSON->new->allow_nonref->decode($good_json)
0 HASH(0xe4b158)
'return' => HASH(0xe4b1b8)
'local' => JSON::XS::Boolean=SCALAR(0xd22f00)
-> 1
'name' => HASH(0xe4afd8)
'name' => 'Sam'
'sync_enabled' => JSON::XS::Boolean=SCALAR(0xd22fd8)
-> 0

zsh filename globbling/substitution

I am trying to create my first zsh completion script, in this case for the command netcfg.
Lame as it may sound I have stuck on the first hurdle, disclaimer, I know how to do this crudely, however I seek the "ZSH WAY" to do this.
I need to list the files in /etc/networking but only the files, not the directory component, so I do the following.
echo $(ls /etc/network.d/*(.))
/etc/network.d/ethernet-dhcp /etc/network.d/wireless-wpa-config
What I wanted was:
ethernet-dhcp wireless-wpa-config
So I try (excuse my naivity) :
echo ${(s/*\/)$(ls /etc/network.d/*(.))}
/etc/network.d/ethernet-dhcp /etc/network.d/wireless-wpa-config
It seems that this doesn't work, I'm sure there must be some clever way of doing this by splitting into an array and getting the last part but as I say, I'm complete noob at this.
Any advice gratefully received.
General note: There is no need to use ls to generate the filenames. You might as well use echo some*glob. But if you want to protect the possible embedded newline characters even that is a bad idea. The first example below globs directly into an array to protect embedded newlines. The second one uses printf to generate NUL terminated data to accomplish the same thing without using a variable.
It is easy to do if you are willing to use a variable:
typeset -a entries
entries=(/etc/network.d/*(.)) # generate the list
echo ${entries#/etc/network.d/} # strip the prefix from each one
You can also do it without a variable, but the extra stuff to isolate individual entries is a bit ugly:
# From the inside, to the outside:
# * glob the entries
# * NUL terminate them into a single string
# * split at NUL
# * strip the prefix from each one
echo ${${(0)"$(printf '%s\0' /etc/network.d/*(.))"}#/etc/network.d/}
Or, if you are going to use a subshell anyway (i.e. the command substitution in the previous example), just cd to the directory so it is not part of the glob expansion (plus, you do not have to repeat the directory name):
echo ${(0)"$(cd /etc/network.d && printf '%s\0' *(.))"}
Chris Johnsen's answer is full of useful information about zsh, however it doesn't mention the much simpler solution that works in this particular case:
echo /etc/network.d/*(:t)
This is using the t history modifier as a glob qualifier.
Thanks for your suggestions guys, having done yet more reading of ZSH and coming back to the problem a couple of days later, I think I've got a very terse solution which I would like to share for your benefit.
echo ${$(print /etc/network.d/*(.)):t}
I'm used to seeing basename(1) stripping off directory components; also, you can use echo /etc/network/* to get the file listing without running the external ls program. (Running external programs can slow down completion more than you'd like; I didn't find a zsh-builtin for basename, but that doesn't mean that there isn't one.)
Here's something I hope will help:
haig% for f in /etc/network/* ; do basename $f ; done
if-down.d
if-post-down.d
if-pre-up.d
if-up.d
interfaces