LinuxQuestions.org

LinuxQuestions.org (/questions/)
-   Programming (https://www.linuxquestions.org/questions/programming-9/)
-   -   Puzzling behavior of YES command (https://www.linuxquestions.org/questions/programming-9/puzzling-behavior-of-yes-command-4175453412/)

danielbmartin 03-09-2013 11:48 AM

Puzzling behavior of YES command
 
This code ...
Code:

echo; echo "Case 1"
yes ' -' |head -n4
echo; echo "Case 2"
yes '--' |head -n4
echo; echo "Case 3"
yes '- ' |head -n4

... produces this result ...
Code:

Case 1
 -
 -
 -
 -

Case 2
y
y
y
y

Case 3
yes: invalid option -- ' '
Try `yes --help' for more information.

Case 1 produces the expected result.
Case 2 produces an unexpected result, but didn't blow up.
Case 3 blows up.

Please advise.

Daniel B. Martin

David the H. 03-09-2013 12:05 PM

In case 1, the first (and only) argument starts with a blank, and so it is treated as a simple text string. In cases 2 & 3, however, it starts with the hyphen, meaning that the command tries to treat it as an option flag.

The '--' in case 2 is treated as the "last option" option, clearing the way for the non-option arguments. But since there are no following arguments it uses the default 'y'

In case 3 '- ' is simply an invalid argument.


The way around it is to always use '--' first when the first non-option argument starts with a hyphen.

Code:

yes -- '--' |head -n4

yes -- '- ' |head -n4

This is true of most commands. You'd do the same, for example, if you needed to rm a filename that started with a hyphen. See the common options section of info coreutils.


Edit: BTW, here's a quick&dirty way to get the same output using bash only. At least with low numbers of lines.
Code:

printf '%s\n' '- '{,,,}
The brace expansion expands into one hyphen string for each comma-separated blank space.

danielbmartin 03-09-2013 01:46 PM

Thank you, David the H., for this explanation.

A follow-on question... I was groping around hoping to find a Linux equivalent for the REXX built-in function COPIES, and thought yes would serve. As you explained, that works for many character strings but not all.

This is how COPIES works:
Code:

COPIES('FOO',3)  returns 'FOOFOOFOO'        (Without the quote marks)
COPIES('*',16)  returns '****************' (Without the quote marks)
COPIES('Bar ',2) returns 'Bar Bar '        (Without the quote marks)

Is there a Linux command which suits this purpose?

Daniel B. Martin

linosaurusroot 03-09-2013 02:07 PM

Quote:

Originally Posted by danielbmartin (Post 4908127)
Code:

COPIES('FOO',3)  returns 'FOOFOOFOO'        (Without the quote marks)
COPIES('*',16)  returns '****************' (Without the quote marks)
COPIES('Bar ',2) returns 'Bar Bar '        (Without the quote marks)

Is there a Linux command which suits this purpose?


Code:

perl -e 'print "Foo"x3; print "\n"'

H_TeXMeX_H 03-09-2013 02:19 PM

Code:

bash-4.2$ copies() { for i in $(seq 1 "$2"); do printf "$1"; done }
bash-4.2$ copies FOO 3
FOOFOOFOObash-4.2$
bash-4.2$ copies '*' 16
****************bash-4.2$
bash-4.2$ copies 'Bar ' 2
Bar Bar bash-4.2$


millgates 03-09-2013 02:29 PM

Slightly shorter printf solution:

Code:

printf "$s%.0s" $(seq 1 $n)

grail 03-10-2013 12:17 PM

Ruby is basically the same as perl in its format:
Code:

ruby -e 'puts "foo" * 3'
Awk is by no means short, but works:
Code:

awk 'BEGIN{OFS="foo";$3="foo";print}'
I would be curious, does REXX's function print the original examples as expected? I would assume yes as it is not shell environment using a command, such as yes.

danielbmartin 03-10-2013 12:35 PM

Quote:

Originally Posted by grail (Post 4908596)
Awk is by no means short, but works:
Code:

awk 'BEGIN{OFS="foo";$3="foo";print}'

Clever, I like it, and found a way to make it shorter.
This code ...
Code:

echo
awk 'BEGIN{OFS="foo";$3="foo";print}'
echo
awk 'BEGIN{OFS="foo";$4="";print}'

... produced this result ...
Code:


foofoofoo

foofoofoo

Daniel B. Martin

danielbmartin 03-10-2013 01:32 PM

Quote:

Originally Posted by grail (Post 4908596)
I would be curious, does REXX's function print the original examples as expected?

I copied those three examples from the Regina manual. Regina is one of the Freeware implementations of REXX for the PC platform.

In response to your question I coded those examples in a trivial REXX program.
This code ...
Code:

say COPIES('FOO',3)
say COPIES('*',16)
say COPIES('Bar ',2)

... produced this result ...
Code:

FOOFOOFOO
****************
Bar Bar

(Note: say is equivalent to echo.)

Daniel B. Martin

grail 03-10-2013 01:51 PM

Sorry Daniel, I was unclear, I meant the dash examples in the original question, but as I said, I would guess it would not be affected as it is not a command with switches, which is why "yes" was affected.

danielbmartin 03-10-2013 02:56 PM

(Inadvertent double post deleted.)

danielbmartin 03-10-2013 03:04 PM

Quote:

Originally Posted by grail (Post 4908651)
... I meant the dash examples in the original question ...

This REXX code ...
Code:

say COPIES(' -',4)
say COPIES('--',4)
say COPIES('- ',4)

... produced this result ...
Code:

- - - -
--------
- - - -

No idiosyncratic behavior because of dashes.

Daniel B. Martin

danielbmartin 03-10-2013 07:31 PM

Quote:

Originally Posted by David the H. (Post 4908066)
The way around it is to always use '--' first when the first non-option argument starts with a hyphen.
Code:

yes -- '--' |head -n4

yes -- '- ' |head -n4


Several solutions have been offered and I like this best. Simple, straightforward, gets the job done. Thank you!

Daniel B. Martin

NevemTeve 03-11-2013 01:28 PM

> The way around it is to always use '--' first when the first non-option argument starts with a hyphen.

And when the argument is a parameter/variable:

Code:

someprogram -- "$1"
much safer.

David the H. 03-12-2013 08:51 AM

Well, if all you wanted was to duplicate a string 'n' times, you should've said so! ;)

Code:

$ printf -v string '%03d' ; echo "${string//0/foo}"
foofoofoo

Edit: Here's the same thing wrapped up in a small function:

Code:

#$1 is the string to copy
#$2 is the number of times to print it (defaults to one)
copies(){
    local str
    printf -v str '%0*d' "${2:-1}"
    printf '%s\n' "${str//0/$1}"
}

The use of "*" in the %d format string tells it to use the value of the first argument as the padding number, rather than as a printing input.


All times are GMT -5. The time now is 09:20 AM.