可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
I'm trying to create a Bash script that will extract the last parameter given from the command line into a variable to be used elsewhere. Here's the script I'm working on:
#!/bin/bash # compact - archive and compact file/folder(s) eval LAST=\$$# FILES="$@" NAME=$LAST # Usage - display usage if no parameters are given if [[ -z $NAME ]]; then echo "compact ... .tar.gz" exit fi # Check if an archive name has been given if [[ -f $NAME ]]; then echo "File exists or you forgot to enter a filename. Exiting." exit fi tar -czvpf "$NAME".tar.gz $FILES
Since the first parameters could be of any number, I have to find a way to extract the last parameter, (e.g. compact file.a file.b file.d files-a-b-d.tar.gz). As it is now the archive name will be included in the files to compact. Is there a way to do this?
回答1:
To remove the last item from the array you could use something like this:
#!/bin/bash length=$(($#-1)) array=${@:1:$length} echo $array
回答2:
回答3:
Several solutions have already been posted; however I would advise restructuring your script so that the archive name is the first parameter rather than the last. Then it's really simple, since you can use the shift
builtin to remove the first parameter:
ARCHIVENAME="$1" shift # Now "$@" contains all of the arguments except for the first
回答4:
This is how I do in my scripts
last=${@:$#} # last parameter other=${*%${!#}} # all parameters except the last
I like because is a very compact solution
回答5:
Thanks guys, got it done, heres the final bash script:
#!/bin/bash # compact - archive and compress file/folder(s) # Extract archive filename for variable ARCHIVENAME="${!#}" # Remove archive filename for file/folder list to backup length=$(($#-1)) FILES=${@:1:$length} # Usage - display usage if no parameters are given if [[ -z $@ ]]; then echo "compact ... .tar.gz" exit fi # Tar the files, name archive after last file/folder if no name given if [[ ! -f $ARCHIVENAME ]]; then tar -czvpf "$ARCHIVENAME".tar.gz $FILES; else tar -czvpf "$ARCHIVENAME".tar.gz "$@" fi
回答6:
Just dropping the length
variable used in Krzysztof Klimonda's solution:
( set -- 1 2 3 4 5 echo "${@:1:($#-1)}" # 1 2 3 4 echo "${@:(-$#):($#-1)}" # 1 2 3 4 )
回答7:
I would add this as a comment, but don't have enough reputation and the answer got a bit longer anyway. Hope it doesn't mind.
As @func stated:
last_arg="${!#}"
How it works:
${!PARAM} indicates level of indirection. You are not referencing PARAM itself, but the value stored in PARAM ( think of PARAM as pointer to value ).
${#} expands to the number of parameters (Note: $0 - the script name - is not counted here).
Consider following execution:
$./myscript.sh p1 p2 p3
And in the myscript.sh
#!/bin/bash echo "Number of params: ${#}" # 3 echo "Last parameter using '\${!#}': ${!#}" # p3 echo "Last parameter by evaluating positional value: $(eval LASTP='$'${#} ; echo $LASTP)" # p3
Hence you can think of ${!#} as a shortcut for the above eval usage, which does exactly the approach described above - evaluates the value stored in the given parameter, here the parameter is 3 and holds the positional argument $3
Now if you want all the params except the last one, you can use substring removal ${PARAM%PATTERN} where % sign means 'remove the shortest matching pattern from the end of the string'.
Hence in our script:
echo "Every parameter except the last one: ${*%${!#}}"
You can read something in here: Parameter expansion
回答8:
#!/bin/bash lastidx=$# lastidx=`expr $lastidx - 1` eval last='$'{$lastidx} echo $last
回答9:
Try:
if [ "$#" -gt '0' ]; then /bin/echo "${!#}" "${@:1:$(($# - 1))} fi
回答10:
#!/bin/sh eval last='$'$# while test $# -gt 1; do list="$list $1" shift done echo $list $last
回答11:
I can't find a way to use array-subscript notation on $@
, so this is the best I can do:
#!/bin/bash args=("$@") echo "${args[$(($#-1))]}"
回答12:
Are you sure this fancy script is any better than a simple alias to tar?
alias compact="tar -czvpf"
Usage is:
compact ARCHIVENAME FILES...
Where FILES can be file1 file2
or globs like *.html
回答13:
This script may work for you - it returns a subrange of the arguments, and can be called from another script.
Examples of it running:
$ args_get_range 2 -2 y a b "c 1" d e f g 'b' 'c 1' 'd' 'e' $ args_get_range 1 2 n arg1 arg2 arg1 arg2 $ args_get_range 2 -2 y arg1 arg2 arg3 "arg 4" arg5 'arg2' 'arg3' $ args_get_range 2 -1 y arg1 arg2 arg3 "arg 4" arg5 'arg2' 'arg3' 'arg 4' # You could use this in another script of course # by calling it like so, which puts all # args except the last one into a new variable # called NEW_ARGS NEW_ARGS=$(args_get_range 1 -1 y "$@")
args_get_range.sh
#!/usr/bin/env bash function show_help() { IT=" Extracts a range of arguments from passed in args and returns them quoted or not quoted. usage: START END QUOTED ARG1 {ARG2} ... e.g. # extract args 2-3 $ args_get_range.sh 2 3 n arg1 arg2 arg3 arg2 arg3 # extract all args from 2 to one before the last argument $ args_get_range.sh 2 -1 n arg1 arg2 arg3 arg4 arg5 arg2 arg3 arg4 # extract all args from 2 to 3, quoting them in the response $ args_get_range.sh 2 3 y arg1 arg2 arg3 arg4 arg5 'arg2' 'arg3' # You could use this in another script of course # by calling it like so, which puts all # args except the last one into a new variable # called NEW_ARGS NEW_ARGS=\$(args_get_range.sh 1 -1 \"\$@\") " echo "$IT" exit } if [ "$1" == "help" ] then show_help fi if [ $# -lt 3 ] then show_help fi START=$1 END=$2 QUOTED=$3 shift; shift; shift; if [ $# -eq 0 ] then echo "Please supply a folder name" exit; fi # If end is a negative, it means relative # to the last argument. if [ $END -lt 0 ] then END=$(($#+$END)) fi ARGS="" COUNT=$(($START-1)) for i in "${@:$START}" do COUNT=$((COUNT+1)) if [ "$QUOTED" == "y" ] then ARGS="$ARGS '$i'" else ARGS="$ARGS $i" fi if [ $COUNT -eq $END ] then echo $ARGS exit; fi done echo $ARGS