Mam zmienną w moim skrypcie bash, której wartość jest mniej więcej taka:
~/a/b/c
Zwróć uwagę, że jest to nierozwinięta tylda. Kiedy robię ls -lt na tej zmiennej (nazwij ją $ VAR), nie otrzymuję takiego katalogu. Chcę pozwolić bashowi zinterpretować / rozszerzyć tę zmienną bez jej wykonywania. Innymi słowy, chcę, aby bash uruchamiał eval, ale nie uruchamiał ocenianego polecenia. Czy to możliwe w bash?
Jak udało mi się przekazać to do mojego skryptu bez rozszerzenia? Przekazałem argument, otaczając go podwójnymi cudzysłowami.
Wypróbuj to polecenie, aby zobaczyć, co mam na myśli:
ls -lt "~"
Dokładnie w takiej sytuacji jestem. Chcę, aby tylda została rozszerzona. Innymi słowy, czym powinienem zastąpić magię, aby te dwa polecenia były identyczne:
ls -lt ~/abc/def/ghi
i
ls -lt $(magic "~/abc/def/ghi")
Zauważ, że ~ / abc / def / ghi może istnieć lub nie.
źródło
eval
.foo=~/"$filepath"
lubfoo="$HOME/$filepath"
dir="$(readlink -f "$dir")"
Odpowiedzi:
Ze względu na naturę StackOverflow nie mogę po prostu uczynić tej odpowiedzi nieakceptowaną, ale w ciągu ostatnich 5 lat, odkąd to opublikowałem, pojawiły się o wiele lepsze odpowiedzi niż moja, co prawda, szczątkowa i całkiem zła odpowiedź (byłem młody, nie zabijaj mnie).
Pozostałe rozwiązania w tym wątku są bezpieczniejsze i lepsze. Najlepiej wybrałbym jedną z tych dwóch:
Oryginalna odpowiedź do celów historycznych (ale nie używaj jej)
Jeśli się nie mylę,
"~"
nie zostanie w ten sposób rozwinięty przez skrypt bash, ponieważ jest traktowany jako dosłowny ciąg"~"
. W ten sposób możesz wymusić ekspansjęeval
.#!/bin/bash homedir=~ eval homedir=$homedir echo $homedir # prints home path
Alternatywnie, po prostu użyj,
${HOME}
jeśli chcesz katalog domowy użytkownika.źródło
${HOME}
najbardziej atrakcyjne. Czy jest jakiś powód, aby nie traktować tego jako głównej rekomendacji? W każdym razie dzięki!eval
jest okropną sugestią, naprawdę źle, że zbiera tak wiele pozytywnych głosów. Gdy wartość zmiennej zawiera metaznaki powłoki, napotkasz różnego rodzaju problemy.Jeśli zmienna
var
jest wprowadzany przez użytkownika,eval
powinien nie być stosowany w celu rozszerzenia za pomocą tyldyeval var=$var # Do not use this!
Powód jest taki: użytkownik może przez przypadek (lub celowo) typ, na przykład
var="$(rm -rf $HOME/)"
z możliwymi katastrofalnymi konsekwencjami.Lepszym (i bezpieczniejszym) sposobem jest użycie rozszerzenia parametrów Bash:
var="${var/#\~/$HOME}"
źródło
#
w"${var/#\~/$HOME}"
?$var
.\~
np. Ucieczki~
? (2) Twoja odpowiedź zakłada, że~
jest to pierwszy znak w$var
. Jak możemy zignorować wiodące białe spacje$var
?Plagarowanie się na podstawie wcześniejszej odpowiedzi , aby zrobić to solidnie bez zagrożeń bezpieczeństwa związanych z
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%:}" }
...użyty jako...
path=$(expandPath '~/hello')
Alternatywnie, prostsze podejście, które wykorzystuje
eval
ostrożnie: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 }
źródło
printf %q
do ucieczki wszystko oprócz tyldy, a następnie używaćeval
bez ryzyka.Bezpiecznym sposobem korzystania z eval jest
"$(printf "~/%q" "$dangerous_path")"
. Zauważ, że jest to specyficzne dla basha.#!/bin/bash relativepath=a/b/c eval homedir="$(printf "~/%q" "$relativepath")" echo $homedir # prints home path
Zobacz to pytanie, aby uzyskać szczegółowe informacje
Zwróć też uwagę, że w przypadku zsh byłoby to tak proste, jak
echo ${~dangerous_path}
źródło
echo ${~root}
daj mi brak wyjścia na zsh (mac os x)export test="~root/a b"; echo ${~test}
Co powiesz na to:
path=`realpath "$1"`
Lub:
path=`readlink -f "$1"`
źródło
realpath
polecenia w C. Dla przykładu, można wygenerować plik wykonywalnyrealpath.exe
za pomocą bash i gcc z tej linii komend:gcc -o realpath.exe -x c - <<< $'#include <stdlib.h> \n int main(int c,char**v){char p[9999]; realpath(v[1],p); puts(p);}'
. Pozdrawiamrealpath ~
->/home/myhome
<workingdir>/~
.Rozszerzanie (gra słów nie jest zamierzona) na odpowiedziach birryree i halloleo: Ogólne podejście polega na użyciu
eval
, ale wiąże się z kilkoma ważnymi zastrzeżeniami, a mianowicie spacjami i przekierowaniem wyjścia (>
) w zmiennej. Wydaje mi się, że działa dla mnie:mypath="$1" if [ -e "`eval echo ${mypath//>}`" ]; then echo "FOUND $mypath" else echo "$mypath NOT FOUND" fi
Spróbuj z każdym z następujących argumentów:
'~' '~/existing_file' '~/existing file with spaces' '~/nonexistant_file' '~/nonexistant file with spaces' '~/string containing > redirection' '~/string containing > redirection > again and >> again'
Wyjaśnienie
${mypath//>}
Wycina>
znaków, które mogłyby sprać plik czasieeval
.eval echo ...
jest to, co robi faktyczna ekspansja tyldy-e
argumentu służą do obsługi nazw plików ze spacjami.Być może jest bardziej eleganckie rozwiązanie, ale to właśnie udało mi się wymyślić.
źródło
$(rm -rf .)
.>
znaki?Myślę, że właśnie tego szukasz
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.$$ }
Przykładowe użycie:
źródło
printf %q
nie wymyka się wiodącym tyldom - prawie kuszące jest zgłoszenie tego jako błędu, ponieważ jest to sytuacja, w której zawodzi w określonym celu. Jednak w międzyczasie dobry telefon!Oto moje rozwiązanie:
#!/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"
źródło
echo
oznacza, żeexpandTilde -n
nie będzie zachowywał się zgodnie z oczekiwaniami, a zachowanie z nazwami plików zawierającymi odwrotne ukośniki jest niezdefiniowane przez POSIX. Zobacz pubs.opengroup.org/onlinepubs/009604599/utilities/echo.htmlPo prostu używaj
eval
poprawnie: z walidacją.case $1${1%%/*} in ([!~]*|"$1"?*[!-+_.[:alnum:]]*|"") ! :;; (*/*) set "${1%%/*}" "${1#*/}" ;; (*) set "$1" esac&& eval "printf '%s\n' $1${2+/\"\$2\"}"
źródło
printf %q
aby uciec od rzeczy bezpiecznie, niż ufam odręcznemu kodowi walidacyjnemu, aby nie zawierał błędów .printf
jest$PATH
poleceniem 'd.bash
? Jeśli tak,printf
jest elementem wbudowanym i%q
na pewno będzie obecny.bash
wystarczająco dużo wcześniej, by wiedzieć, że mu nie ufam. spróbuj:x=$(printf \\1); [ -n "$x" ] || echo but its not null!
Najprostsze : zamień „magię” na „eval echo”.
$ eval echo "~" /whatever/the/f/the/home/directory/is
Problem: Będziesz mieć problemy z innymi zmiennymi, ponieważ eval jest zły. Na przykład:
$ # home is /Users/Hacker$(s) $ s="echo SCARY COMMAND" $ eval echo $(eval echo "~") /Users/HackerSCARY COMMAND
Zauważ, że problem z wtryskiem nie występuje przy pierwszym rozszerzeniu. Więc jeśli były po prostu zastąpić
magic
zeval echo
, powinno być w porządku. Ale jeśli to zrobiszecho $(eval echo ~)
, będzie to podatne na zastrzyk.Podobnie, jeśli to zrobisz
eval echo ~
zamiast tegoeval echo "~"
, liczy się to jako dwukrotne rozwinięcie, a zatem wstrzyknięcie będzie możliwe od razu.źródło
s='echo; EVIL_COMMAND'
. (Nie powiedzie się, ponieważEVIL_COMMAND
nie istnieje na twoim komputerze. Ale gdyby to polecenie byłorm -r ~
na przykład, spowodowałoby usunięcie twojego katalogu domowego.)Oto funkcja równoważna z POSIX Hakon Hægland za bash odpowiedź
expand_tilde() { tilde_less="${1#\~/}" [ "$1" != "$tilde_less" ] && tilde_less="$HOME/$tilde_less" printf '%s' "$tilde_less" }
2017-12-10 edycja: dodaj
'%s'
w komentarzach @CharlesDuffy.źródło
printf '%s\n' "$tilde_less"
, być może? W przeciwnym razie będzie źle działać, jeśli rozwijana nazwa pliku zawiera ukośniki odwrotne%s
lub inną składnię, która ma znaczenie dlaprintf
. Poza tym jest to jednak świetna odpowiedź - poprawna (gdy rozszerzenia bash / ksh nie muszą być zakryte), oczywiście bezpieczna (bez manipulowaniaeval
) i zwięzła.dlaczego nie zagłębić się bezpośrednio w pobieranie katalogu domowego użytkownika za pomocą getent?
źródło
Wystarczy rozszerzyć odpowiedź birryree na ścieżki o spacje: Nie można użyć
eval
polecenia w takiej postaci, w jakiej jest, ponieważ oddziela ocenę spacjami. Jednym z rozwiązań jest tymczasowe zastąpienie spacji dla polecenia 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
Ten przykład opiera się oczywiście na założeniu, że
mypath
nigdy nie zawiera sekwencji znaków"_spc_"
.źródło
$(rm -rf .)
Może się to wydawać łatwiejsze w Pythonie.
(1) Z wiersza poleceń unixa:
python -c 'import os; import sys; print os.path.expanduser(sys.argv[1])' ~/fred
Prowadzi do:
(2) W ramach skryptu bash jednorazowo - zapisz to jako
test.sh
:#!/usr/bin/env bash thepath=$(python -c 'import os; import sys; print os.path.expanduser(sys.argv[1])' $1) echo $thepath
Uruchamianie
bash ./test.sh
wyników w:(3) Jako narzędzie - zapisz to
expanduser
gdzieś na swojej ścieżce, z uprawnieniami do wykonywania:#!/usr/bin/env python import sys import os print os.path.expanduser(sys.argv[1])
Można to następnie użyć w wierszu poleceń:
Lub w scenariuszu:
#!/usr/bin/env bash thepath=$(expanduser $1) echo $thepath
źródło
echo $thepath
jest buggy; musiecho "$thepath"
naprawiać mniej rzadkie przypadki (nazwy z tabulatorami lub ciągami spacji są konwertowane na pojedyncze spacje; nazwy z rozszerzeniami globów) lubprintf '%s\n' "$thepath"
też naprawiać rzadkie (np. plik o nazwie-n
lub plik z literały odwrotnego ukośnika w systemie zgodnym z XSI). Podobniethepath=$(expanduser "$1")
echo
na zachowanie w sposób całkowicie zdefiniowany przez implementację, jeśli którykolwiek argument zawiera ukośniki odwrotne; opcjonalne rozszerzenia XSI do domyślnych (nie-e
lub-E
wymagane) rozszerzenia mandatu POSIX dla takich nazw.Zrobiłem to z podstawieniem parametrów zmiennych po wczytaniu ścieżki za pomocą read -e (między innymi). Dzięki temu użytkownik może uzupełnić ścieżkę za pomocą tabulatora, a jeśli użytkownik wprowadzi ścieżkę ~, zostanie ona posortowana.
read -rep "Enter a path: " -i "${testpath}" testpath testpath="${testpath/#~/${HOME}}" ls -al "${testpath}"
Dodatkową korzyścią jest to, że jeśli nie ma tyldy, nic się nie dzieje ze zmienną, a jeśli istnieje tylda, ale nie na pierwszej pozycji, jest ona również ignorowana.
(Dołączam -i do odczytu, ponieważ używam tego w pętli, aby użytkownik mógł naprawić ścieżkę, jeśli wystąpi problem).
źródło