Làm thế nào để biến bash global thành một chuỗi biến?


14

Thông tin hệ thống

HĐH: HĐH

bash: GNU bash, phiên bản 3.2.57 (1) -release (x86_64-apple-darwin16)

Lý lịch

Tôi muốn cỗ máy thời gian loại trừ một tập hợp các thư mục và tệp khỏi tất cả dự án git / nodejs của tôi. Các thư mục dự án của tôi nằm trong ~/code/private/~/code/public/vì vậy tôi đang cố gắng sử dụng bash loop để thực hiện tmutil.

Vấn đề

Phiên bản ngắn

Nếu tôi có một biến chuỗi được tính toánk , làm thế nào để tôi biến nó thành toàn cầu trong hoặc ngay trước vòng lặp for:

i='~/code/public/*'
j='*.launch'
k=$i/$j # $k='~/code/public/*/*.launch'

for i in $k # I need $k to glob here
do
    echo $i
done

Trong phiên bản dài dưới đây, bạn sẽ thấy k=$i/$j. Vì vậy, tôi không thể mã hóa chuỗi trong vòng lặp for.

Phiên bản dài

#!/bin/bash
exclude='
*.launch
.classpath
.sass-cache
Thumbs.db
bower_components
build
connect.lock
coverage
dist
e2e/*.js
e2e/*.map
libpeerconnection.log
node_modules
npm-debug.log
testem.log
tmp
typings
'

dirs='
~/code/private/*
~/code/public/*
'

for i in $dirs
do
    for j in $exclude
    do
        k=$i/$j # It is correct up to this line

        for l in $k # I need it glob here
        do
            echo $l
        #   Command I want to execute
        #   tmutil addexclusion $l
        done
    done
done

Đầu ra

Họ không phải là toàn cầu. Không phải những gì tôi muốn.

~/code/private/*/*.launch                                                                                   
~/code/private/*/.DS_Store                                                                                  
~/code/private/*/.classpath                                                                                 
~/code/private/*/.sass-cache                                                                                
~/code/private/*/.settings                                                                                  
~/code/private/*/Thumbs.db                                                                                  
~/code/private/*/bower_components                                                                           
~/code/private/*/build                                                                                      
~/code/private/*/connect.lock                                                                               
~/code/private/*/coverage                                                                                   
~/code/private/*/dist                                                                                       
~/code/private/*/e2e/*.js                                                                                   
~/code/private/*/e2e/*.map                                                                                  
~/code/private/*/libpeerconnection.log                                                                      
~/code/private/*/node_modules                                                                               
~/code/private/*/npm-debug.log                                                                              
~/code/private/*/testem.log                                                                                 
~/code/private/*/tmp                                                                                        
~/code/private/*/typings                                                                                    
~/code/public/*/*.launch                                                                                    
~/code/public/*/.DS_Store                                                                                   
~/code/public/*/.classpath                                                                                  
~/code/public/*/.sass-cache                                                                                 
~/code/public/*/.settings                                                                                   
~/code/public/*/Thumbs.db                                                                                   
~/code/public/*/bower_components                                                                            
~/code/public/*/build                                                                                       
~/code/public/*/connect.lock                                                                                
~/code/public/*/coverage                                                                                    
~/code/public/*/dist                                                                                        
~/code/public/*/e2e/*.js                                                                                    
~/code/public/*/e2e/*.map                                                                                   
~/code/public/*/libpeerconnection.log                                                                       
~/code/public/*/node_modules                                                                                
~/code/public/*/npm-debug.log                                                                               
~/code/public/*/testem.log                                                                                  
~/code/public/*/tmp                                                                                         
~/code/public/*/typings

Các trích dẫn đơn dừng nội suy shell trong Bash, vì vậy bạn có thể thử trích dẫn hai lần biến của mình.
Thomas N

@ThomasN không, điều đó không hoạt động. klà một chuỗi được tính toán và tôi cần nó giữ nguyên như vậy cho đến vòng lặp. Vui lòng kiểm tra phiên bản dài của tôi.
John Siu

@ThomasN Tôi đã cập nhật phiên bản ngắn để làm cho nó rõ ràng hơn.
John Siu

Câu trả lời:


18

Bạn có thể buộc một vòng đánh giá khác eval, nhưng điều đó không thực sự cần thiết. (Và evalbắt đầu gặp vấn đề nghiêm trọng ngay khi tên tệp của bạn chứa các ký tự đặc biệt như $.) Vấn đề không phải là trên toàn cầu, mà là việc mở rộng dấu ngã.

Globbing xảy ra sau khi mở rộng biến, nếu biến không được trích dẫn, như ở đây (*) :

$ x="/tm*" ; echo $x
/tmp

Vì vậy, trong cùng một hướng, điều này tương tự với những gì bạn đã làm và hoạt động:

$ mkdir -p ~/public/foo/ ; touch ~/public/foo/x.launch
$ i="$HOME/public/*"; j="*.launch"; k="$i/$j"
$ echo $k
/home/foo/public/foo/x.launch

Nhưng với dấu ngã thì không:

$ i="~/public/*"; j="*.launch"; k="$i/$j"
$ echo $k
~/public/*/*.launch

Điều này được ghi lại rõ ràng cho Bash:

Thứ tự mở rộng là: mở rộng cú đúp; mở rộng dấu ngã, mở rộng tham số và biến, ...

Mở rộng dấu ngã xảy ra trước khi mở rộng biến vì vậy dấu ngã bên trong biến không được mở rộng. Cách giải quyết dễ dàng là sử dụng $HOMEhoặc đường dẫn đầy đủ thay thế.

(* mở rộng các khối từ các biến thường không phải là những gì bạn muốn)


Cái khác:

Khi bạn lặp qua các mẫu, như ở đây:

exclude="foo *bar"
for j in $exclude ; do
    ...

lưu ý rằng như không $excludeđược trích dẫn, cả hai đều bị tách ra và cũng được đặt toàn cầu vào thời điểm này. Vì vậy, nếu thư mục hiện tại chứa một cái gì đó phù hợp với mẫu, nó sẽ được mở rộng sang đó:

$ i="$HOME/public/foo"
$ exclude="*.launch"
$ touch $i/real.launch
$ for j in $exclude ; do           # split and glob, no match
    echo "$i"/$j ; done
/home/foo/public/foo/real.launch

$ touch ./hello.launch
$ for j in $exclude ; do           # split and glob, matches in current dir!
    echo "$i"/$j ; done
/home/foo/public/foo/hello.launch  # not the expected result

Để khắc phục điều này, hãy sử dụng biến mảng thay vì chuỗi bị tách:

$ exclude=("*.launch")
$ exclude+=("something else")
$ for j in "${exclude[@]}" ; do echo "$i"/$j ; done
/home/foo/public/foo/real.launch
/home/foo/public/foo/something else

Là một phần thưởng bổ sung, các mục mảng cũng có thể chứa khoảng trắng mà không gặp vấn đề với việc chia tách.


Một cái gì đó tương tự có thể được thực hiện với find -path, nếu bạn không quan tâm đến mức độ thư mục các tệp được nhắm mục tiêu. Ví dụ: để tìm bất kỳ đường dẫn kết thúc bằng /e2e/*.js:

$ dirs="$HOME/public $HOME/private"
$ pattern="*/e2e/*.js"
$ find $dirs -path "$pattern"
/home/foo/public/one/two/three/e2e/asdf.js

Chúng ta phải sử dụng $HOMEthay vì ~cùng một lý do như trước đây và $dirscần phải bỏ trích dẫn trên finddòng lệnh để nó bị phân tách, nhưng $patternnên được trích dẫn để nó không bị mở rộng bởi shell.

(Tôi nghĩ rằng bạn có thể chơi với -maxdepthGNU find để giới hạn mức độ tìm kiếm đi sâu, nếu bạn quan tâm, nhưng đó là một vấn đề khác.)


Bạn có phải là một câu trả lời với find? Tôi thực sự đang khám phá tuyến đường đó, vì vòng lặp for đang trở nên phức tạp. Nhưng tôi đang gặp khó khăn với '-path'.
John Siu

Tín dụng cho bạn vì thông tin của bạn về dấu ngã '~' trực tiếp hơn cho vấn đề chính. Tôi sẽ đăng kịch bản cuối cùng và giải thích trong một câu trả lời khác. Nhưng tín dụng đầy đủ cho bạn: D
John Siu

@JohnSiu, yeah, sử dụng find là điều đầu tiên bạn nghĩ đến. Nó cũng có thể được sử dụng, tùy thuộc vào nhu cầu chính xác. (hoặc tốt hơn, đối với một số mục đích sử dụng.)
ilkkachu

1
@kevinarpe, tôi nghĩ rằng các mảng về cơ bản chỉ có nghĩa là như vậy, và vâng, "${array[@]}"(với các trích dẫn!) được ghi lại (xem ở đâyở đây ) để mở rộng thành các yếu tố như các từ riêng biệt mà không cần tách chúng ra thêm.
ilkkachu

1
@sixtyfive, tốt, [abc]là một phần tiêu chuẩn của mô hình toàn cầu , như ?, tôi không nghĩ cần phải đi đến tất cả chúng ở đây.
ilkkachu

4

Bạn có thể lưu nó dưới dạng một mảng thay vì một chuỗi để sử dụng sau này trong nhiều trường hợp và để cho việc tạo hình xảy ra khi bạn xác định nó. Trong trường hợp của bạn, ví dụ:

k=(~/code/public/*/*.launch)
for i in "${k[@]}"; do

hoặc trong ví dụ sau, bạn sẽ cần evalmột số chuỗi

dirs=(~/code/private/* ~/code/public/*)
for i in "${dirs[@]}"; do
    for j in $exclude; do
        eval "for k in $i/$j; do tmutil addexclusion \"\$k\"; done"
    done
done

1
Lưu ý cách $excludechứa các ký tự đại diện, bạn cần phải vô hiệu hóa toàn cầu trước khi sử dụng toán tử split + global trên nó và khôi phục nó cho $i/$jkhông sử dụng evalnhưng sử dụng"$i"/$j
Stéphane Chazelas

Cả bạn và ilkkachu đều đưa ra câu trả lời tốt. Tuy nhiên, câu trả lời của ông đã xác định vấn đề. Vì vậy, tín dụng cho anh ta.
John Siu

2

@ilkkachu trả lời giải quyết vấn đề toàn cầu chính. Tín dụng đầy đủ cho anh.

V1

Tuy nhiên, do excludecó chứa các mục cả có và không có ký tự đại diện (*), và chúng cũng có thể không tồn tại trong tất cả, nên cần kiểm tra thêm sau khi kết nối $i/$j. Tôi đang chia sẻ những phát hiện của tôi ở đây.

#!/bin/bash
exclude="
*.launch
.DS_Store
.classpath
.sass-cache
.settings
Thumbs.db
bower_components
build
connect.lock
coverage
dist
e2e/*.js
e2e/*.map
libpeerconnection.log
node_modules
npm-debug.log
testem.log
tmp
typings
"

dirs="
$HOME/code/private/*
$HOME/code/public/*
"

# loop $dirs
for i in $dirs; do
    for j in $exclude ; do
        for k in $i/$j; do
            echo -e "$k"
            if [ -f $k ] || [ -d $k ] ; then
                # Only execute command if dir/file exist
                echo -e "\t^^^ Above file/dir exist! ^^^"
            fi
        done
    done
done

Giải thích đầu ra

Sau đây là đầu ra một phần để giải thích tình hình.

/Volumes/HD2/JS/code/public/simple-api-example-ng2-express/a.launch
    ^^^ Above file/dir exist! ^^^
/Volumes/HD2/JS/code/public/simple-api-example-ng2-express/b.launch
    ^^^ Above file/dir exist! ^^^
/Volumes/HD2/JS/code/public/simple-api-example-ng2-express/.DS_Store
    ^^^ Above file/dir exist! ^^^

Trên đây là tự giải thích.

/Volumes/HD2/JS/code/public/simple-api-example-ng2-express/.classpath
/Volumes/HD2/JS/code/public/simple-api-example-ng2-express/.sass-cache
/Volumes/HD2/JS/code/public/simple-api-example-ng2-express/.settings
/Volumes/HD2/JS/code/public/simple-api-example-ng2-express/Thumbs.db
/Volumes/HD2/JS/code/public/simple-api-example-ng2-express/bower_components
/Volumes/HD2/JS/code/public/simple-api-example-ng2-express/build
/Volumes/HD2/JS/code/public/simple-api-example-ng2-express/connect.lock
/Volumes/HD2/JS/code/public/simple-api-example-ng2-express/coverage
/Volumes/HD2/JS/code/public/simple-api-example-ng2-express/dist

Ở trên hiển thị vì mục loại trừ ( $j) không có ký tự đại diện, $i/$jtrở thành một chuỗi nối đơn giản. Tuy nhiên, tập tin / dir không tồn tại.

/Volumes/HD2/JS/code/public/simple-api-example-ng2-express/e2e/*.js
/Volumes/HD2/JS/code/public/simple-api-example-ng2-express/e2e/*.map

Ở trên hiển thị dưới dạng mục nhập loại trừ ( $j) chứa ký tự đại diện nhưng không khớp tệp / thư mục, toàn cầu $i/$jchỉ trả về chuỗi gốc.

V2

V2 sử dụng trích dẫn duy nhất, evalshopt -s nullglobđể có được kết quả sạch. Không yêu cầu kiểm tra tập tin / thư mục cuối cùng.

#!/bin/bash
exclude='
*.launch
.sass-cache
Thumbs.db
bower_components
build
connect.lock
coverage
dist
e2e/*.js
e2e/*.map
libpeerconnection.log
node_modules
npm-debug.log
testem.log
tmp
typings
'

dirs='
$HOME/code/private/*
$HOME/code/public/*
'

for i in $dirs; do
    for j in $exclude ; do
        shopt -s nullglob
        eval "k=$i/$j"
        for l in $k; do
            echo $l
        done
        shopt -u nullglob
    done
done

Một vấn đề là trong đó for j in $exclude, các khối trong $excludecó thể được mở rộng tại thời điểm $excludemở rộng đó (và kêu gọi evalđó là yêu cầu sự cố). Bạn muốn kích hoạt toàn cầu cho for i in $dir, và for l in $k, nhưng không phải cho for j in $exclude. Bạn muốn một cái set -ftrước và cái set +fkia. Nói chung, bạn muốn điều chỉnh toán tử chia + toàn cầu trước khi sử dụng nó. Trong mọi trường hợp, bạn không muốn chia + global cho echo $l, vì vậy $lnên được trích dẫn ở đó.
Stéphane Chazelas

@ StéphaneChazelas bạn đang giới thiệu đến v1 hoặc v2? Đối với v2, cả hai excludedirsđều trong một trích dẫn ( ), so no globbing till eval`.
John Siu

Globbing diễn ra khi mở rộng biến không được trích dẫn trong ngữ cảnh danh sách , rằng (để lại một biến không được trích dẫn) là cái mà đôi khi chúng ta gọi là toán tử split + global . Không có sự phân bổ trong các bài tập cho các biến vô hướng. foo=*foo='*'là như nhau. Nhưng echo $fooecho "$foo"không (trong các shell như bash, nó đã được cố định trong các shell như zsh, fish hoặc RC, xem thêm liên kết ở trên). Ở đây bạn làm muốn sử dụng toán tử đó, nhưng ở một số nơi chỉ phần chia, và những người khác chỉ là phần glob.
Stéphane Chazelas 7/10/2016

@ StéphaneChazelas Cảm ơn thông tin !!! Đã cho tôi đôi khi nhưng tôi hiểu mối quan tâm bây giờ. Điều này rất có giá trị !! Cảm ơn bạn!!!
John Siu

1

Với zsh:

exclude='
*.launch
.classpath
.sass-cache
Thumbs.db
...
'

dirs=(
~/code/private/*
~/code/public/*
)

for f ($^dirs/${^${=~exclude}}(N)) {
  echo $f
}

${^array}stringlà mở rộng như $array[1]string $array[2]string.... $=varlà để thực hiện phân tách từ trên biến (một cái vỏ khác làm theo mặc định!), $~varcó phải là toàn cầu trên biến (cái vỏ khác cũng theo mặc định (khi bạn thường không muốn chúng, bạn phải trích dẫn $fở trên vỏ khác)).

(N)là một vòng loại toàn cầu bật nullglob cho mỗi khối đó do $^array1/$^array2sự mở rộng đó . Điều đó làm cho các khối được mở rộng thành không có gì khi chúng không khớp. Điều đó cũng xảy ra để biến một phi thế giới ~/code/private/foo/Thumbs.dbthành một, điều đó có nghĩa là nếu điều đó không tồn tại, thì nó không được bao gồm.


Điều này thực sự tốt đẹp. Tôi đã thử nghiệm và làm việc. Tuy nhiên, dường như zsh nhạy cảm hơn với dòng mới khi sử dụng trích dẫn đơn. Cách excludeđược kèm theo là ảnh hưởng đến đầu ra.
John Siu

@JohnSiu, ồ, đúng rồi. Có vẻ như phân tách + toàn cầu và $^arrayphải được thực hiện theo hai bước riêng biệt để đảm bảo các phần tử trống được loại bỏ (xem chỉnh sửa). Trông hơi giống một lỗi trong zsh, tôi sẽ nêu vấn đề trong danh sách gửi thư của họ.
Stéphane Chazelas

Tôi nghĩ ra một v2 cho bash, sạch hơn, nhưng vẫn không nhỏ gọn như tập lệnh zsh của bạn, lol
John Siu
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.