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

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}"
}

Related

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

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!

fishshell checking equvalancy. (like `==` does in most languages)

All I want to do is perform a simple equivalency check like you would in other languages with the == operator. It would look something like
if $var == 0
echo Hello world!
end
I'm a really surprised that the closest thing in the docs I could find is contains which allows for this silly makeshift solution
if contains $var 0
echo Hello world!
end
Use the test builtin, like
if test "$var" = 0
# do stuff
end
test is also available as [, in which case it expects the last argument to be ], so you can write
if [ "$var" = 0 ]
# do stuff
end
I'm quoting the variable here because tests argument parsing (which was taken straight from POSIX) doesn't work well with fish's lists, so if $var doesn't have exactly one element it will break with surprising errors.

use identifying symbols to identify and edit line/string, then append line/string to previous line in file

Using standard linux utilities (sed and awk, I am guessing)
Sorry about the vague title, I don't really know how to describe the request much better. An easier way to do so is to provide a simple example. I have a file with the following content:
www.example.com
johnsmith#gmail.com
fredflintstone#gmail.com
bettyboop#gmail.com
www.example2.com
kylejohnson#gmail.com
www.example3.com
chadbrown#gmail.com
joshbeck#gmail.com
www.example4.com
tomtom#gmail.com
jeffjeffries#gmail.com
billnorman#gmail.com
stankubrick#gmail.com
andrewanders#gmail.com
So, what I want to do is convert the above to:
www.example.com,johnsmith#gmail.com,fredflintstone#gmail.com,bettyboop#gmail.com
www.example2.com,kylejohnson#gmail.com
www.example3.com,chadbrown#gmail.com,joshbeck#gmail.com,
www.example4.com,tomtom#gmail.com,jeffjeffries#gmail.com,billnorman#gmail.com,stankubrick#gmail.com,andrewanders#gmail.com
I am thinking that the easiest thing to do would be to execute something along the lines of: if the line contains an "#" symbol, input a comma at the beginning of the line/string and then append that line/string to the preceding line. Anyone have any ideas? It would be simpler, I think, if there were a uniform number of email addresses associated with each website, but this is not the case.
Thanks in advance!
A simple approach
awk '{s=/#/?",":"\n";printf s"%s",$0}' file
www.example.com,johnsmith#gmail.com,fredflintstone#gmail.com,bettyboop#gmail.com
www.example2.com,kylejohnson#gmail.com
www.example3.com,chadbrown#gmail.com,joshbeck#gmail.com
s=/#/?",":"\n" Does line contain # yes set s="," no set s="\n" (newline).
printf s"%s",$0 print $0 using s as format. If line has # print newline, then $0, if not print ,, then $0
Try this awk program:
/^[:space:]*www\./ {
if (f) {print line}
f=1; line=$0;
next
}
f {
line=(line "," $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

To fix a bug in Less' command by noting no. of parameters

This question is based on this thread.
The code
function man()
{
man "$1" > /tmp/manual; less /tmp/manual
}
Problem: if I use even one option, the command does not know where is the wanted-manual
For instance,
man -k find
gives me an error, since the reference is wrong. The command reads -k as the manual.
My attempt to solve the problem in pseudo-code
if no parameters
run the initial code
if one parameter
run: man "$2"
...
In other words, we need to add an option-check to the beginning such that
Pseudo-code
man $optional-option(s) "$n" > /tmp/manual; less /tmp/manual
where $n
n=1 if zero options
n=2 if 1 option
n=3 if 2 options
....
How can you make such an "option-check" that you can alter the value of $n?
Developed Problem: to make two if loops for the situations from n=1 to n=2
How about passing all the arguments
function man()
{
man $# > /tmp/manual; less /tmp/manual
}
What is the bug in less which you mention in the title?
First, you can pass all of your function's arguments to man by using $* or $#. You can read man sh for the precise details on the difference between the two; short story is to almost always use "$#" with double quotes.
Second, the temporary file is unnecessary. You could make this a little cleaner by piping the output of man directly to less:
function man() {
man "$#" | less
}
By the way, if you're just trying to use a different pager (man uses more and you want the fancier less) there's a commonly recognized PAGER environment variable that you can set to override the default pager. You could add this to your ~/.bashrc for instance to tell all programs to use less when displaying multiple screens of output:
export PAGER=less
To answer your precise question, you can check the number of arguments with $#:
if [ $# -eq 0 ]; then
: # No arguments
elif [ $# -eq 1 ]; then
: # One argument
# etc.
You might also find the shift command helpful. It renames $2 to $1, $3 to $2, and so on. It is often used in a loop to process command-line arguments one by one:
while [ $# -gt 1 ]; do
echo "Next argument is: $1"
shift
done
echo "Last argument is: $1"