I'm trying to write a code that moves files to a .junk directory using the built-in environment variables. My goal is that all a user would have to do is type sh junk filename1 filename2 filename3... and the script would use $1, $2, $3... to move the files. Unfortunately I can't quite get the counter loop to work correctly.


if [ $# = 0 ]
then echo "Usage: junk -l | -p | { filename }*"

elif [ $1 = "-l" ] && [ $HOME/.junk ]
     then ls $HOME/.junk

elif [ $1 = "-p" ] && [ $HOME/.junk ]
     then rm -r $HOME/.junk

elif [ $1 = "$@" ] && [ $HOME/.junk ]
     then echo $#


while [ $i -le $# ]
 echo $s$i
 mv $s$i .junk/$i

  • 1
    What are "built-in environment variables"? Which shell are you using /bin/s? – Anthon Apr 28 '14 at 21:36
  • That is terrible code. $1 should always be quoted: "$1". What is [ $HOME/.junk ] supposed to be/do? And [ $1 = "$@" ]? Why does while [ $i -le $# ] start with i=1 instead of i=0? What is supposed to happen with a single file argument? mv $s$i is probably supposed to be an (unquotes, of course) indirection which would have to be done with eval or (better) "${!i}"... The counter loop really is the least of your problems. – Hauke Laging Apr 28 '14 at 21:44
  • Why are you using a loop in the first place? All you really need is mv "$@" .junk. Could you explain what your script is doing and why you want the loop? – terdon Apr 28 '14 at 21:52
  • @terdon there is one barely possible chance that mv "$@" ./junk will present argument list size problems, but that would take a lot of junk. – mikeserv Apr 29 '14 at 0:07
  • @mikeserv It is likely that calling the script would be a problem too in that case. – Kusalananda Feb 13 '19 at 15:07

Some immediate problems:

  1. Use getopts for argument processing or have a reason why not.
  2. If the script is given more than 1 argument, the "$@" in the third elif will blow it up.
  3. Use a for f in "$@" loop instead of a while loop.


  1. What happens if you type sh junk --help ... or some more interesting mv option?
  2. QED
  3. Simpler => Better
  • 1
    for f in "$@" ; do is hardly simpler than for f ; do which is the same thing when you're iterating on positionals. Also while and until allow conditionals - as many as you can put in a compound command list, thought I don't think our asker is making good use of it. One I always use for positionals is while ${1+:} false ; do which is pretty simple. It also gives me the freedom of skipping iterations with shift as needed. – mikeserv Apr 29 '14 at 18:15
  • @mikeserv Good comment, but it would be a better answer. Just add one sentence at the top pointing out that attempting to treat positional parameters like an array is the wrong idea. It might actually be useful to some other bash beginner and I'll upvote it past my grumpy answer. – Scott Leadley Apr 29 '14 at 18:32
  • Positional parameters are an array. The shell positional $@array is the only fully portable shell array there is. And with its special IFS handling its really the only one you need. – mikeserv Apr 29 '14 at 19:29


while getopts 'lp' opt; do
    case "$opt" in
        l) do_list=true  ;;
        p) do_purge=true ;;
            printf 'Usage: %s [-l|-p] [ name ... ]\n' "$0" >&2
            exit 1

shift "$(( OPTIND - 1 ))"

if [ ! -d "$junkdir" ] && ! "$do_purge"; then
    printf '%s: Missing junk directory "%s"\n' "$0" "$junkdir" >&2
    printf '%s: Re-run with -p to create it\n' "$0" >&2
    exit 1

if "$do_list"; then
    printf 'Current junk in "%s":\n' "$junkdir"
    ls -l "$junkdir"

if "$do_purge"; then
    printf 'Emptying junk directory "%s"\n' "$junkdir"
    rm -rf "$junkdir"
    mkdir "$junkdir"

if [ "$#" -eq 0 ]; then

echo 'Junking the following things:'
printf '%s\n' "$@"

mv "$@" "$junkdir"

This script implements what it is you're trying to do. It uses getopts to parse the command line flags -l and/or -p.

I'm setting do_list and do_purge in the while loop that parses the command line option because I want to control the order in which the ls and the rm takes place further down. If a user uses -lp or -pl I want them to see the contents of the junk directory before it is removed.

Removing the junk directory with -p also recreates it. I do this with rm -rf followed by mkdir for simplicity.

After the command line parsing loop, I adjust the positional parameters so that they only contain non-option arguments (names of files and/or directories).

If there are no positional arguments ($# is zero), I don't try to run mv to move files to the junk directory.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.