10

Goal

In ZSH script, for a given args, I want to obtain the first string and the rest.

For instance, when the script is named test

sh test hello

supposed to extract h and ello.

ZSH manual

http://zsh.sourceforge.net/Doc/zsh_a4.pdf

says:

Subscripting may also be performed on non-array values, in which case the subscripts specify a substring to be extracted. For example, if FOO is set to ‘foobar’, then ‘echo $FOO[2,5]’ prints ‘ooba’.

Q1

So, I wrote a shell script in a file named test

echo $1
echo $1[1,1]

terminal:

$ sh test hello
hello
hello[1,1]

the result fails. What's wrong with the code?

Q2

Also I don't know how to extract subString from n to the last. Perhaps do I have to use Array split by regex?

EDIT: Q3

This may be another question, so if it's proper to start new Thread, I will do so.

Thanks to @skishore Here is the further code

#! /bin/zsh

echo $1

ARG_FIRST=`echo $1 | cut -c1`
ARG_REST=`echo $1 | cut -c2-`
echo ARG_FIRST=$ARG_FIRST
echo ARG_REST=$ARG_REST

if $ARG_FIRST = ""; then
  echo nullArgs
else
  if $ARG_FIRST = "@"; then
    echo @Args
  else
    echo regularArgs
  fi
fi

I'm not sure how to compare string valuables to string, but for a given args hello

result:

command not found: h

What's wrong with the code?

EDIT2:

What I've found right

#! /bin/zsh

echo $1

ARG_FIRST=`echo $1 | cut -c1`
ARG_REST=`echo $1 | cut -c2-`
echo ARG_FIRST=$ARG_FIRST
echo ARG_REST=$ARG_REST

if [ $ARG_FIRST ]; then
  if [ $ARG_FIRST = "@" ]; then
    echo @Args
  else
    echo regularArgs
  fi
else
  echo nullArgs
fi

EDIT3:

As the result of whole, this is what I've done with this question.

https://github.com/kenokabe/GitSnapShot

GitSnapShot is a ZSH thin wrapper for Git commands for easier and simpler usage

3 Answers3

12

A1

As others have said, you need to wrap it in curly braces. Also, use a command interpreter (#!...), mark the file as executable, and call it directly.

#!/bin/zsh

echo $1
echo ${1[1,1]}

A2

The easiest way to extract a substring from a parameter (zsh calls variables parameters) is to use parameter expansion. Using the square brackets tells zsh to treat the scalar (i.e. string) parameter as an array. For a single character, this makes sense. For the rest of the string, you can use the simpler ${parameter:start:length} notation instead. If you omit the :length part (as we will here), then it will give you the rest of the scalar.

File test:

#!/bin/zsh

echo ${1[1]}
echo ${1:1}

Terminal:

$ ./test Hello
H
ello

A3

As others have said, you need (preferably double) square brackets to test. Also, to test if a string is NULL use -z, and to test if it is not NULL use -n. You can just put a string in double brackets ([[ ... ]]), but it is preferable to make your intentions clear with -n.

if [[ -z "${ARG_FIRST}" ]]; then
  ...
fi

Also remove the space between #! and /bin/zsh. And if you are checking for equality, use ==; if you are assigning a value, use =.

RE:EDIT2:

  • Declare all parameters to set the scope. If you do not, you may clobber or use a parameter inherited from the shell, which may cause unexpected behavior. Google's shell style guide is a good resource for stuff like this.
  • Use builtins over external commands.
  • Avoid backticks. Use $(...) instead.
  • Use single quotes when quoting a literal string. This prevents pattern matching.
  • Make use of elif or case to avoid nested ifs. case will be easier to read in your example here, but elif will probably be better for your actual code.

Using case:

#!/bin/zsh

typeset ARG_FIRST="${1[1]}"
typeset ARG_REST="${1:1}"

echo $1
echo 'ARG_FIRST='"${ARG_FIRST}"
echo 'ARG_REST='"${ARG_REST}"

case "${ARG_FIRST}" in
  ('') echo 'nullArgs' ;;
  ('@') echo '@Args' ;;
  (*)
    # Recommended formatting example with more than 1 sloc
    echo 'regularArgs'
    ;;
esac

using elif:

#!/bin/zsh

typeset ARG_FIRST="${1[1]}"
typeset ARG_REST="${1:1}"

echo $1
echo 'ARG_FIRST='"${ARG_FIRST}"
echo 'ARG_REST='"${ARG_REST}"

if [[ -z "${ARG_FIRST}" ]]; then
  echo nullArgs
elif [[ '@' == "${ARG_FIRST}" ]]; then
  echo @Args
else
  echo regularArgs
fi

RE:EDIT3

  • Use "$@" unless you really know what you are doing. Explanation.
a172
  • 185
  • 2
  • 7
  • Excellent answer . It not only answers the OP's question, but it also provides many good insights for how to improve coding in shell. – juanmirocks Dec 02 '22 at 12:22
11

You can use the cut command:

echo $1 | cut -c1 
echo $1 | cut -c2-

Use $() to assign these values to variables:

ARG_FIRST=$(echo $1 | cut -c1)
ARG_REST=$(echo $1 | cut -c2-)
echo ARG_FIRST=$ARG_FIRST
echo ARG_REST=$ARG_REST

You can also replace $() with backticks, but the former is recommended and the latter is somewhat deprecated due to nesting issues.

Dessa Simpson
  • 1,232
  • 1
  • 15
  • 30
disatisfieddinosaur
  • 1,502
  • 10
  • 15
  • Thanks @skishore, your code works, but how can I store the result to, say, argFirst and argRest? –  Aug 12 '13 at 00:45
  • 1
    Not sure how to format comments nicely, so I edited my answer. – disatisfieddinosaur Aug 12 '13 at 00:52
  • eah, I just did without quotation.. Works perfect. You are cool. Thank s a lot! –  Aug 12 '13 at 00:54
  • I have another quesion, and if you have a thought, plz let me know @skishore –  Aug 12 '13 at 01:30
  • 1
    See here for how to compare strings: http://stackoverflow.com/questions/2237080/how-can-i-compare-strings-in-bash-script Basically the comparison lines should look like `if [[ "$ARG_FIRST" == "" ]]; then` – disatisfieddinosaur Aug 12 '13 at 01:35
  • Thanks again @skishore , I sort of found the solution like that; please see my EDIT2:, it works with [ ... ], but you also suggest to use [[ ... ]], what is the difference, and my solution should be rather avoided?? –  Aug 12 '13 at 01:44
  • The brackets just increase the allowable syntax within the clause (for example, they enable && and ||). http://stackoverflow.com/questions/2188199/how-to-use-double-or-single-bracket-parentheses-curly-braces – disatisfieddinosaur Aug 12 '13 at 01:45
  • Ok, so for the single condition, [[ ... ]] is extra and not required to use I understand. Am I correct here. –  Aug 12 '13 at 01:52
7

So, I wrote a shell script in a file named test

$ sh test hello

This isn't a zsh script: you're calling it with sh, which is (almost certainly) bash. If you've got the shebang (#!/bin/zsh), you can make it executable (chmod +x <script>) and run it: ./script. Alternatively, you can run it with zsh <script>.

the result fails. What's wrong with the code?

You can wrap in braces:

echo ${1}        # This'll work with or without the braces.
echo ${1[3,5]}   # This works in the braces. 
echo $1[3,5]     # This doesn't work. 

Running this: ./test-script hello gives:

./test-script.zsh hello 
hello
llo
./test-script.zsh:5: no matches found: hello[3,5]

Also I don't know how to extract subString from n to the last. Perhaps do I have to use Array split by regex?

Use the [n,last] notation, but wrap in braces. We can determine how long our variable is with, then use the length:

# Store the length of $1 in LENGTH. 
LENGTH=${#1}
echo ${1[2,${LENGTH}]}  # Display from `2` to `LENGTH`. 

This'll produce ello (prints from the 2nd to the last character of hello).

Script to play with:

#!/usr/local/bin/zsh

echo ${1}      # Print the input
echo ${1[3,5]} # Print from 3rd->5th characters of input
LENGTH=${#1}   
echo ${1[2,${LENGTH}]} # Print from 2nd -> last characters of input.

You can use the cut command:

But that would be using extra baggage - zsh is quite capable of doing all this on it's own without spawning multiple sub-shells for simplistic operations.

simont
  • 68,704
  • 18
  • 117
  • 136
  • 1
    Debian & Ubuntu nowadays have `sh` link to the more minimal `dash` (not not `bash` as stated above). (See: [Dash as /bin/sh](https://wiki.ubuntu.com/DashAsBinSh)) – zrajm Sep 06 '13 at 08:54
  • 2
    You don't need the `LENGTH` thing, you can just use `-1` instead. As in `${1[2;-1]}` – kralyk Jul 01 '14 at 14:11