Извлечь параметры перед последним параметром в "$ @"

Я пытаюсь создать Bash script, который будет извлекать последний параметр из командной строки в переменную, которая будет использоваться в другом месте. Здесь script Я работаю над:

#!/bin/bash
# compact - archive and compact file/folder(s)

eval LAST=\$$#

FILES="[email protected]"
NAME=$LAST

# Usage - display usage if no parameters are given
if [[ -z $NAME ]]; then
  echo "compact <file> <folder>... <compressed-name>.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

Так как первые параметры могут быть любого числа, я должен найти способ извлечь последний параметр (например, файл file.b file.b.d.gar). Так как теперь имя архива будет включено в файлы для компактности. Есть ли способ сделать это?

Ответ 1

Чтобы удалить последний элемент из массива, вы можете использовать что-то вроде этого:

#!/bin/bash

length=$(($#-1))
array=${@:1:$length}
echo $array

Ответ 2

last_arg="${!#}" 

Ответ 3

Несколько решений уже опубликованы; однако я бы посоветовал реструктурировать ваш script, чтобы имя архива было первым параметром, а не последним. Тогда это действительно просто, так как вы можете использовать shift встроенный, чтобы удалить первый параметр:

ARCHIVENAME="$1"
shift
# Now "[email protected]" contains all of the arguments except for the first

Ответ 4

Портативные и компактные решения

Вот как я делаю в своих сценариях

last=${@:$#} # last parameter 
other=${*%${!#}} # all parameters except the last

РЕДАКТИРОВАТЬ
Согласно некоторым комментариям (см. Ниже), это решение является более портативным, чем другие.
Пожалуйста, прочитайте комментарий Майкла Диммитта для объяснения того, как это работает.

Ответ 5

Спасибо, ребята, все сделано, heres 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 [email protected] ]]; then
  echo "compact <file> <folder>... <compressed-name>.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 "[email protected]"
fi

Ответ 6

Просто отбросив переменную length, используемую в решении Krzysztof Klimonda:

(
set -- 1 2 3 4 5
echo "${@:1:($#-1)}"       # 1 2 3 4
echo "${@:(-$#):($#-1)}"   # 1 2 3 4
)

Ответ 7

Я бы добавил это как комментарий, но не имел достаточной репутации, и ответ получил немного больше в любом случае. Надеюсь, что это не против.

Как сказал @func:

last_arg = "$ {!} #"

Как это работает:

${! PARAM} указывает уровень косвенности. Вы не ссылаетесь на PARAM самостоятельно, а на значение, хранящееся в PARAM (считайте PARAM как указатель на значение).
${#} расширяется до количества параметров (Примечание: $0 - имя script - здесь не учитывается).

Рассмотрим следующее выполнение:

$./myscript.sh p1 p2 p3

И в 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

Следовательно, вы можете думать о ${! #} как ярлыке для вышеупомянутого использования eval, которое делает именно описанный выше подход, - оценивает значение, хранящееся в данном параметре, здесь параметр 3 и содержит аргумент position $3

Теперь, если вам нужны все параметры, кроме последнего, вы можете использовать удаление подстроки ${PARAM% PATTERN}, где знак% означает "удалить кратчайший шаблон соответствия от конца строки",

Следовательно, в нашем script:

echo "Every parameter except the last one: ${*%${!#}}"


Здесь вы можете прочитать что-то: Расширение параметров

Ответ 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

Вы уверены, что этот причудливый script не лучше простого псевдонима для tar?

alias compact="tar -czvpf"

Использование:

compact ARCHIVENAME FILES...

Где FILES может быть file1 file2 или globs как *.html

Ответ 11

#!/bin/sh

eval last='$'$#
while test $# -gt 1; do
    list="$list $1"
    shift
done

echo $list $last

Ответ 12

Я не могу найти способ использовать нотацию на основе массива на [email protected], так что это лучшее, что я могу сделать:

#!/bin/bash

args=("[email protected]")
echo "${args[$(($#-1))]}"

Ответ 13

Этот script может работать для вас - он возвращает поддиапазон аргументов и может быть вызван из другого script.

Примеры запуска:

$ 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 "[email protected]")

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 \"\[email protected]\")

  "
  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

Ответ 14

Массив без последнего параметра:

array=${@:1:$#-1}

Но это bashism :(. Правильные решения будут включать сдвиг и добавление в переменную, как используют другие.

Ответ 15

Альтернативный способ извлечь последний параметр из списка аргументов:

eval last="\$$#"
eval set -- 'awk 'BEGIN{for(i=1;i<'$#';i++) printf " \"$%d\"",i;}''