Isso faz o que você quer, e também é deliberadamente mais capaz do que estritamente precisa ser.
Como você disse que era estudante, eu queria não apenas responder à sua pergunta, mas também criar um exemplo bastante simples de como usar getopts
para processar opções e argumentos da linha de comando ... e também como um pouco mais trabalho com opções pode estender a funcionalidade básica para adicionar alguns recursos úteis.
As opções -e
, -v
, -i
, -H
e -h
são as mesmas usadas em grep
e algumas outras ferramentas comuns, para que os usuários se beneficiem do conhecimento existente e não utilizem não precisa aprender opções novas e incompatíveis.
Para acelerar várias pesquisas dos mesmos arquivos .zip, o script também armazena em cache a saída de unzip -v
para cada arquivo (em /var/tmp/
por padrão). Opções de linha de comando -c
e -C
podem ser usadas para limpar os arquivos de cache antes ou depois (ou ambos) da pesquisa.
Finalmente, usei aspas duplas em torno do uso ALL das variáveis exceto nos casos específicos em que as aspas duplas podem causar problemas - por exemplo, quando eles possuem argumentos opcionais para o comando grep
- sem aspas, eles não adicionam nada aos argumentos que serão passados para grep
, mas se fossem citados com aspas duplas, eles adicionariam a sequência vazia a esses argumentos. Este é um exemplo de um dos poucos casos em que você não deve citar duas vezes suas variáveis. Em todos os outros casos, use aspas duplas.
Nota: como apontado pelo G-Man, a única razão pela qual é razoavelmente seguro usar $IGNORECASE
sem aspas é porque eu o configurei explicitamente para um valor conhecido e seguro (isto é, sem espaços ou asteriscos ou outros caracteres problemáticos) antes que eu usei, então eu sei para o fato de que não pode conter qualquer outro valor. Esse certo conhecimento me permitiu preguiça de citar neste caso em particular.
No entanto, seria mais seguro usar ${IGNORECASE:+"$IGNORECASE"}
, especialmente se ele pudesse conter um valor arbitrário desconhecido (por exemplo, atribuído a partir da linha de comando em vez de codificado no script).
BTW, ${varname:+"$varname"}
retorna absolutamente nada (nem mesmo a string vazia) se $varname
estiver vazio OU o valor com aspas duplas de $varname
se não estiver vazio.
Use o script assim:
$ ./searchzip.sh -h -e Tom file*.zip
113 Defl:N 64 43% 2016-05-29 15:45 cf747915 a/Tom.txt
113 Defl:N 64 43% 2016-05-29 15:45 cf747915 tomato/Tom.txt
ou:
$ ./searchzip.sh -i -e Tom file*.zip
file1.zip: 113 Defl:N 64 43% 2016-05-29 15:45 cf747915 a/Tom.txt
file2.zip: 113 Defl:N 64 43% 2016-05-29 15:45 cf747915 b/tom.txt
file3.zip: 113 Defl:N 64 43% 2016-05-29 15:45 cf747915 c/tom3.txt
file4.zip: 0 Stored 0 0% 2016-05-29 15:50 00000000 tomato/
file4.zip: 113 Defl:N 64 43% 2016-05-29 15:45 cf747915 tomato/Tom.txt
ou:
$ ./searchzip.sh -i -e Tom file*.zip | awk -F: '{print $1}' | sort -u
file1.zip
file2.zip
file3.zip
file4.zip
De qualquer forma, aqui está o script:
#!/bin/bash
#set -x
# 1. define usage() function to print help
usage() {
[ -n "$*" ] && echo "$@" $'\n' > /dev/stderr
cat > /dev/stderr <<__EOF__
Usage: $0 [-HhicC] [-d cachedir ] [-e PATTERN] [ -v PATTERN ] zipfile...
-e Pattern to search for
-v Pattern to exclude from search
-i Ignore case when searching
-H Include .zip filenames in output (default)
-h Suppress .zip filenames in output
-d Directory to use for temporary listing files (default /var/tmp)
-c Delete cache files before searching
-C Delete cache files after searching
-h This help message
Either -e or -v may be specified multiple times
__EOF__
exit 1;
}
# 2. set some defaults
CLEANUP=0
CLEAR=0
IGNORECASE=''
FNAMES='-H'
EXCL=''
pattern=''
exclude=''
cache_dir="/var/tmp"
# 3. process command-line options
while getopts ":s:e:v:d:CchHi" opt; do
case "$opt" in
s|e) pattern+="$OPTARG|" ;; # -s is an undocumented alias for -e
v) exclude+="$OPTARG|" ;;
d) cache_dir="$OPTARG" ;;
C) CLEANUP='1' ;;
c) CLEAR='1' ;;
h) FNAMES='-h' ;;
H) FNAMES='-H' ;;
i) IGNORECASE='-i' ;;
*) usage ;;
esac
done
shift $((OPTIND-1))
# 4. check and post-process options and their args
[ -z "$pattern" ] && usage 'ERROR: -e option is required'
# remove trailing '|' from $pattern and $exclude
pattern="${pattern%|}"
exclude="${exclude%|}"
# 5. the main loop of the program that does all the work
for f in "$@" ; do
if [ -e "$f" ] ; then
cache_file="$cache_dir/$f.list"
search_file="$cache_file.search"
[ "$CLEAR" -eq 1 ] && rm -f "$cache_file"
if [ ! -e "$cache_file" ] ; then
unzip -v "$f" > "$cache_file"
fi
grep "$FNAMES" $IGNORECASE -E "$pattern" "$cache_file" > "$search_file"
# safer to use ${IGNORECASE:+"$IGNORECASE"}
if [ -z "$exclude" ] ; then
sed -e "s/^.*$f[^:]*:/$f:/" "$search_file"
else
sed -e "s/^.*$f[^:]*:/$f:/" "$search_file" |
grep $IGNORECASE -v -E "$exclude"
# or use ${IGNORECASE:+"$IGNORECASE"}
fi
rm -f "$search_file"
[ "$CLEANUP" -eq 1 ] && rm -f "$cache_file"
fi
done
A estrutura básica do programa é:
-
defina uma função
usage()
para imprimir uma mensagem de ajuda (com mensagem de erro opcional) -
defina padrões para algumas variáveis
-
processe as opções da linha de comando
-
execute qualquer verificação de integridade e pós-processamento necessária para essas opções e seus argumentos
-
Finalmente, o loop principal do programa que faz todo o trabalho.
Esta é uma estrutura muito comum e muito simples que você pode usar em muitos programas.
BTW, eu não coloquei nenhum comentário no loop principal. Eu senti que eles seriam redundantes, pois usei nomes de variáveis significativos, de modo que os comentários seriam apenas paráfrases triviais do código, como "# do foo" antes de fazer "foo". Se e quando necessário, eu teria feito comentários sempre que eu sentisse que o código não era auto-explicativo.