Come per espandere manualmente una variabile speciale (es: ~ tilde) in bash
-
09-10-2019 - |
Domanda
Ho una variabile nel mio script bash il cui valore è qualcosa di simile a questo:
~/a/b/c
Si noti che è tilde non espanso. Quando faccio ls -LT su questa variabile (lo chiamano $ VAR), ottengo tale directory. Voglio lasciare bash interpretare / espandere questa variabile senza eseguirlo. In altre parole, voglio bash per eval periodo, ma non eseguire il comando valutata. Questo è possibile in bash?
Come ho fatto a passare questo nel mio script senza espansione? Ho superato l'argomento in circondandolo con virgolette doppie.
Prova questo comando per capire cosa intendo:
ls -lt "~"
Questa è esattamente la situazione in cui mi trovo. Io voglio la tilde per essere ampliato. In altre parole, che cosa devo sostituire la magia con di rendere questi due comandi identici:
ls -lt ~/abc/def/ghi
e
ls -lt $(magic "~/abc/def/ghi")
Si noti che ~ / abc / def / ghi può o non può esistere.
Soluzione
A causa della natura di StackOverflow, non posso solo fare questa risposta non accettato, ma nei successivi 5 anni da quando ho postato questo ci sono state risposte molto meglio di mia risposta certamente rudimentale e piuttosto male (ero giovane, non mi uccide).
Le altre soluzioni in questa discussione sono le soluzioni più sicure e migliori. Preferibilmente, mi piacerebbe andare con uno di questi due:
- La soluzione di Charle Duffy
- Håkon Hægland
risposta originale per scopi storici (ma per favore non usare questo)
Se non mi sbaglio, "~"
non sarà ampliata con uno script bash in quel modo, perché è trattata come un "~"
stringa letterale. È possibile forzare espansione tramite eval
come questo.
#!/bin/bash
homedir=~
eval homedir=$homedir
echo $homedir # prints home path
In alternativa, basta usare ${HOME}
se si desidera che la directory home dell'utente.
Altri suggerimenti
Se la var
variabile è inserita dall'utente, eval
dovrebbe non essere utilizzato per espandere la tilde utilizzando
eval var=$var # Do not use this!
Il motivo è:. L'utente potrebbe accidentalmente (o scopo) tipo per esempio var="$(rm -rf $HOME/)"
con possibili conseguenze disastrose
Una migliore (e più sicuro) modo è quello di utilizzare l'espansione di parametro Bash:
var="${var/#\~/$HOME}"
una precedente risposta , per fare questo robusto senza rischi per la sicurezza associati a eval
:
expandPath() {
local path
local -a pathElements resultPathElements
IFS=':' read -r -a pathElements <<<"$1"
: "${pathElements[@]}"
for path in "${pathElements[@]}"; do
: "$path"
case $path in
"~+"/*)
path=$PWD/${path#"~+/"}
;;
"~-"/*)
path=$OLDPWD/${path#"~-/"}
;;
"~"/*)
path=$HOME/${path#"~/"}
;;
"~"*)
username=${path%%/*}
username=${username#"~"}
IFS=: read -r _ _ _ _ _ homedir _ < <(getent passwd "$username")
if [[ $path = */* ]]; then
path=${homedir}/${path#*/}
else
path=$homedir
fi
;;
esac
resultPathElements+=( "$path" )
done
local result
printf -v result '%s:' "${resultPathElements[@]}"
printf '%s\n' "${result%:}"
}
... usato come ...
path=$(expandPath '~/hello')
In alternativa, un approccio più semplice che usi eval
con attenzione:
expandPath() {
case $1 in
~[+-]*)
local content content_q
printf -v content_q '%q' "${1:2}"
eval "content=${1:0:2}${content_q}"
printf '%s\n' "$content"
;;
~*)
local content content_q
printf -v content_q '%q' "${1:1}"
eval "content=~${content_q}"
printf '%s\n' "$content"
;;
*)
printf '%s\n' "$1"
;;
esac
}
Un modo sicuro per l'uso eval è "$(printf "~/%q" "$dangerous_path")"
. Si noti che è specifico bash.
#!/bin/bash
relativepath=a/b/c
eval homedir="$(printf "~/%q" "$relativepath")"
echo $homedir # prints home path
questo domanda per i dettagli
Si noti inoltre che in zsh questo sarebbe come semplice come echo ${~dangerous_path}
Che ne dite di questo:
path=`realpath "$1"`
o
path=`readlink -f "$1"`
L'espansione (no pun intended) sulle risposte di halloleo birryree di e: L'approccio generale è quello di utilizzare eval
, ma si tratta con alcuni avvertimenti importanti, vale a dire spazi e reindirizzamento uscita (>
) nella variabile. Quanto segue sembra funzionare per me:
mypath="$1"
if [ -e "`eval echo ${mypath//>}`" ]; then
echo "FOUND $mypath"
else
echo "$mypath NOT FOUND"
fi
Prova con ciascuno dei i seguenti argomenti:
'~'
'~/existing_file'
'~/existing file with spaces'
'~/nonexistant_file'
'~/nonexistant file with spaces'
'~/string containing > redirection'
'~/string containing > redirection > again and >> again'
Spiegazione
- Il
${mypath//>}
strisce fuori personaggi>
che potrebbe massacravano un file durante laeval
. - Il
eval echo ...
è ciò che fa l'espansione della tilde reale - Le virgolette intorno l'argomento
-e
sono per il supporto di nomi di file con spazi.
Forse c'è una soluzione più elegante, ma questo è quello che sono riuscito a trovare.
Credo che questo sia quello che stai cercando per
magic() { # returns unexpanded tilde express on invalid user
local _safe_path; printf -v _safe_path "%q" "$1"
eval "ln -sf ${_safe_path#\\} /tmp/realpath.$$"
readlink /tmp/realpath.$$
rm -f /tmp/realpath.$$
}
Esempio di utilizzo:
$ magic ~nobody/would/look/here
/var/empty/would/look/here
$ magic ~invalid/this/will/not/expand
~invalid/this/will/not/expand
Ecco la mia soluzione:
#!/bin/bash
expandTilde()
{
local tilde_re='^(~[A-Za-z0-9_.-]*)(.*)'
local path="$*"
local pathSuffix=
if [[ $path =~ $tilde_re ]]
then
# only use eval on the ~username portion !
path=$(eval echo ${BASH_REMATCH[1]})
pathSuffix=${BASH_REMATCH[2]}
fi
echo "${path}${pathSuffix}"
}
result=$(expandTilde "$1")
echo "Result = $result"
Basta usare eval
correttamente:. Con la convalida
case $1${1%%/*} in
([!~]*|"$1"?*[!-+_.[:alnum:]]*|"") ! :;;
(*/*) set "${1%%/*}" "${1#*/}" ;;
(*) set "$1"
esac&& eval "printf '%s\n' $1${2+/\"\$2\"}"
Questa è la funzione equivalente POSIX di di Håkon Hægland Bash risposta
expand_tilde() {
tilde_less="${1#\~/}"
[ "$1" != "$tilde_less" ] && tilde_less="$HOME/$tilde_less"
printf '%s' "$tilde_less"
}
2017/12/10 edit:. Add '%s'
per @CharlesDuffy nei commenti
Proprio per estendere birryree 's risposta per i percorsi con gli spazi: non è possibile utilizzare il comando eval
come è perché separa la valutazione da spazi. Una soluzione è quella di sostituire gli spazi temporaneamente il comando eval:
mypath="~/a/b/c/Something With Spaces"
expandedpath=${mypath// /_spc_} # replace spaces
eval expandedpath=${expandedpath} # put spaces back
expandedpath=${expandedpath//_spc_/ }
echo "$expandedpath" # prints e.g. /Users/fred/a/b/c/Something With Spaces"
ls -lt "$expandedpath" # outputs dir content
Questo esempio si basa naturalmente sul presupposto che mypath
non contiene mai il "_spc_"
sequenza di char.
Si potrebbe trovare questo più facile da fare in python.
(1) Dalla riga di comando UNIX:
python -c 'import os; import sys; print os.path.expanduser(sys.argv[1])' ~/fred
Risultati in:
/Users/someone/fred
(2) All'interno di uno script bash come un one-off - Salva questo come test.sh
:
#!/usr/bin/env bash
thepath=$(python -c 'import os; import sys; print os.path.expanduser(sys.argv[1])' $1)
echo $thepath
Esecuzione risultati bash ./test.sh
in:
/Users/someone/fred
(3) Come un programma di utilità - Salva questo come expanduser
da qualche parte sul vostro percorso, con i permessi di esecuzione:
#!/usr/bin/env python
import sys
import os
print os.path.expanduser(sys.argv[1])
Questo potrebbe quindi essere utilizzato sulla riga di comando:
expanduser ~/fred
o in uno script:
#!/usr/bin/env bash
thepath=$(expanduser $1)
echo $thepath
più semplice :. Replace 'magia' con 'eval echo'
$ eval echo "~"
/whatever/the/f/the/home/directory/is
Problema: Hai intenzione di incorrere in problemi con le altre variabili, perché eval è il male. Per esempio:
$ # home is /Users/Hacker$(s)
$ s="echo SCARY COMMAND"
$ eval echo $(eval echo "~")
/Users/HackerSCARY COMMAND
Si noti che il problema dell'iniezione non avviene sulla prima espansione. Quindi, se si dovesse sostituire semplicemente magic
con eval echo
, si dovrebbe essere a posto. Ma se lo fai echo $(eval echo ~)
, che sarebbe suscettibile di iniezione.
Allo stesso modo, se si fa eval echo ~
invece di eval echo "~"
, che conterebbe come due volte ampliato e, pertanto, l'iniezione sarebbe possibile fin da subito.
Ho fatto questo con sostituzione di parametro variabile dopo aver letto nel percorso utilizzando -e di lettura (tra gli altri). Così l'utente può, scheda-completare il percorso, e se l'utente inserisce un percorso ~ si ottiene allineati.
read -rep "Enter a path: " -i "${testpath}" testpath
testpath="${testpath/#~/${HOME}}"
ls -al "${testpath}"
Il vantaggio è che se non c'è tilde non succede nulla alla variabile, e se c'è una tilde, ma non nella prima posizione è anche ignorato.
(includo la -i per la lettura da quando uso questo in un ciclo così l'utente può risolvere il percorso se c'è un problema.)